一呼巴、定義
Service 是一個可以在后臺執(zhí)行長時間運行操作而不提供用戶界面的應(yīng)用組件影钉。服務(wù)可由其他應(yīng)用組件啟動季惩,而且即使用戶切換到其他應(yīng)用录粱,服務(wù)仍將在后臺繼續(xù)運行。 此外画拾,組件可以綁定到服務(wù)关摇,以與之進行交互,甚至是執(zhí)行進程間通信 (IPC)碾阁。
簡單地說输虱,服務(wù)是一種即使用戶未與應(yīng)用交互也可在后臺運行的組件。 因此脂凶,您應(yīng)僅在必要時才創(chuàng)建服務(wù)宪睹。
二愁茁、場景需求
服務(wù)可以處理網(wǎng)絡(luò)事務(wù)、播放音樂亭病,執(zhí)行文件 I/O 或與內(nèi)容提供程序交互鹅很,而所有這一切均可在后臺進行。
如需在主線程外部執(zhí)行工作罪帖,不過只是在用戶正在與應(yīng)用交互時才有此需要促煮,則應(yīng)創(chuàng)建新線程而非服務(wù)。 例如整袁,如果您只是想在 Activity 運行的同時播放一些音樂菠齿,則可在 onCreate() 中創(chuàng)建線程,在 onStart() 中啟動線程坐昙,然后在 onStop() 中停止線程绳匀。您還可以考慮使用 AsyncTask 或 HandlerThread,而非傳統(tǒng)的 Thread 類炸客。
請記住疾棵,如果您確實要使用服務(wù),則默認(rèn)情況下痹仙,它仍會在應(yīng)用的主線程中運行是尔,因此,如果服務(wù)執(zhí)行的是密集型或阻止性操作开仰,則您仍應(yīng)在服務(wù)內(nèi)創(chuàng)建新線程。
三抖所、生命周期
服務(wù)的生命周期比 Activity 的生命周期要簡單得多。但是痕囱,密切關(guān)注如何創(chuàng)建和銷毀服務(wù)反而更加重要田轧,因為服務(wù)可以在用戶沒有意識到的情況下運行于后臺。
服務(wù)生命周期(從創(chuàng)建到銷毀)可以遵循兩條不同的路徑:
啟動服務(wù)
該服務(wù)在其他組件調(diào)用 startService() 時創(chuàng)建鞍恢,然后無限期運行傻粘,且必須通過調(diào)用 stopSelf() 來自行停止運行。此外帮掉,其他組件也可以通過調(diào)用 stopService() 來停止服務(wù)弦悉。服務(wù)停止后,系統(tǒng)會將其銷毀蟆炊。
綁定服務(wù)
該服務(wù)在另一個組件(客戶端)調(diào)用 bindService() 時創(chuàng)建稽莉。然后,客戶端通過 IBinder 接口與服務(wù)進行通信涩搓∥鄹眩客戶端可以通過調(diào)用 unbindService() 關(guān)閉連接劈猪。多個客戶端可以綁定到相同服務(wù),而且當(dāng)所有綁定全部取消后良拼,系統(tǒng)即會銷毀該服務(wù)战得。(服務(wù)不必自行停止運行。)
這兩條路徑并非完全獨立庸推。也就是說常侦,您可以綁定到已經(jīng)使用 startService() 啟動的服務(wù)。例如贬媒,可以通過使用 Intent(標(biāo)識要播放的音樂)調(diào)用 startService() 來啟動后臺音樂服務(wù)聋亡。隨后,可能在用戶需要稍加控制播放器或獲取有關(guān)當(dāng)前播放歌曲的信息時掖蛤,Activity 可以通過調(diào)用 bindService() 綁定到服務(wù)杀捻。在這種情況下,除非所有客戶端均取消綁定蚓庭,否則 stopService() 或 stopSelf() 不會實際停止服務(wù)致讥。
與 Activity 類似,服務(wù)也擁有生命周期回調(diào)方法器赞,您可以實現(xiàn)這些方法來監(jiān)控服務(wù)狀態(tài)的變化并適時執(zhí)行工作
左圖顯示了使用 startService() 所創(chuàng)建的服務(wù)的生命周期垢袱,
右圖顯示了使用 bindService() 所創(chuàng)建的服務(wù)的生命周期。
通過實現(xiàn)這些方法港柜,您可以監(jiān)控服務(wù)生命周期的兩個嵌套循環(huán):
服務(wù)的整個生命周期從調(diào)用 onCreate() 開始起请契,到 onDestroy() 返回時結(jié)束。與 Activity 類似夏醉,服務(wù)也在 onCreate() 中完成初始設(shè)置爽锥,并在 onDestroy() 中釋放所有剩余資源。例如畔柔,音樂播放服務(wù)可以在 onCreate() 中創(chuàng)建用于播放音樂的線程氯夷,然后在 onDestroy() 中停止該線程。
無論服務(wù)是通過 startService() 還是 bindService() 創(chuàng)建靶擦,都會為所有服務(wù)調(diào)用 onCreate() 和 onDestroy() 方法腮考。
服務(wù)的有效生命周期從調(diào)用 onStartCommand() 或 onBind() 方法開始。每種方法均有 Intent 對象玄捕,該對象分別傳遞到 startService() 或 bindService()踩蔚。
對于啟動服務(wù),有效生命周期與整個生命周期同時結(jié)束(即便是在 onStartCommand() 返回之后枚粘,服務(wù)仍然處于活動狀態(tài))馅闽。對于綁定服務(wù),有效生命周期在 onUnbind() 返回時結(jié)束。
注:盡管啟動服務(wù)是通過調(diào)用 stopSelf() 或 stopService() 來停止捞蛋,但是該服務(wù)并無相應(yīng)的回調(diào)(沒有 onStop() 回調(diào))孝冒。因此,除非服務(wù)綁定到客戶端拟杉,否則在服務(wù)停止時庄涡,系統(tǒng)會將其銷毀 — onDestroy() 是接收到的唯一回調(diào)。
圖 2 說明了服務(wù)的典型回調(diào)方法搬设。
盡管該圖分開介紹通過 startService() 創(chuàng)建的服務(wù)和通過 bindService() 創(chuàng)建的服務(wù)穴店,但是請記住,不管啟動方式如何拿穴,任何服務(wù)均有可能允許客戶端與其綁定泣洞。因此,最初使用 onStartCommand()(通過客戶端調(diào)用 startService())啟動的服務(wù)仍可接收對 onBind() 的調(diào)用(當(dāng)客戶端調(diào)用 bindService() 時)默色。
以下框架服務(wù)展示了每種生命周期方法:
public class ExampleService extends Service {
int mStartMode; // indicates how to behave if the service is killed
IBinder mBinder; // interface for clients that bind
boolean mAllowRebind; // indicates whether onRebind should be used
@Override
public void onCreate() {
// The service is being created
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
// The service is starting, due to a call to startService()
return mStartMode;
}
@Override
public IBinder onBind(Intent intent) {
// A client is binding to the service with bindService()
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
// All clients have unbound with unbindService()
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
// A client is binding to the service with bindService(),
// after onUnbind() has already been called
}
@Override
public void onDestroy() {
// The service is no longer used and is being destroyed
}
}
生命周期方法具體介紹
主要介紹內(nèi)部調(diào)用方法 & 外部調(diào)用方法的關(guān)系球凰。
常見的生命周期使用
三、服務(wù)創(chuàng)建的兩種方式
1.創(chuàng)建啟動服務(wù)(startService)
啟動服務(wù)由另一個組件通過調(diào)用 startService() 啟動腿宰,這會導(dǎo)致調(diào)用服務(wù)的 onStartCommand() 方法呕诉。
服務(wù)啟動之后,其生命周期即獨立于啟動它的組件吃度,并且可以在后臺無限期地運行甩挫,即使啟動服務(wù)的組件已被銷毀也不受影響。 因此椿每,服務(wù)應(yīng)通過調(diào)用 stopSelf() 結(jié)束工作來自行停止運行伊者,或者由另一個組件通過調(diào)用 stopService() 來停止它。
應(yīng)用組件(如 Activity)可以通過調(diào)用 startService() 方法并傳遞 Intent 對象(指定服務(wù)并包含待使用服務(wù)的所有數(shù)據(jù))來啟動服務(wù)间护。服務(wù)通過 onStartCommand() 方法接收此 Intent亦渗。
例如,假設(shè)某 Activity 需要將一些數(shù)據(jù)保存到在線數(shù)據(jù)庫中汁尺。該 Activity 可以啟動一個協(xié)同服務(wù)法精,并通過向 startService() 傳遞一個 Intent,為該服務(wù)提供要保存的數(shù)據(jù)均函。服務(wù)通過 onStartCommand() 接收 Intent,連接到互聯(lián)網(wǎng)并執(zhí)行數(shù)據(jù)庫事務(wù)菱涤。事務(wù)完成之后苞也,服務(wù)會自行停止運行并隨即被銷毀。
注意:默認(rèn)情況下粘秆,服務(wù)與服務(wù)聲明所在的應(yīng)用運行于同一進程如迟,而且運行于該應(yīng)用的主線程中。 因此,如果服務(wù)在用戶與來自同一應(yīng)用的 Activity 進行交互時執(zhí)行密集型或阻止性操作殷勘,則會降低 Activity 性能此再。 為了避免影響應(yīng)用性能,您應(yīng)在服務(wù)內(nèi)啟動新線程玲销。
從傳統(tǒng)上講输拇,您可以擴展兩個類來創(chuàng)建啟動服務(wù):
Service
這是適用于所有服務(wù)的基類。擴展此類時贤斜,必須創(chuàng)建一個用于執(zhí)行所有服務(wù)工作的新線程策吠,因為默認(rèn)情況下,服務(wù)將使用應(yīng)用的主線程瘩绒,這會降低應(yīng)用正在運行的所有 Activity 的性能猴抹。
IntentService
這是 Service 的子類,它使用工作線程逐一處理所有啟動請求锁荔。如果您不要求服務(wù)同時處理多個請求蟀给,這是最好的選擇。 您只需實現(xiàn) onHandleIntent() 方法即可阳堕,該方法會接收每個啟動請求的 Intent跋理,使您能夠執(zhí)行后臺工作。
2.創(chuàng)建綁定服務(wù)(bindService)
綁定服務(wù)允許應(yīng)用組件通過調(diào)用 bindService() 與其綁定嘱丢,以便創(chuàng)建長期連接(通常不允許組件通過調(diào)用 startService() 來啟動它)薪介。
如需與 Activity 和其他應(yīng)用組件中的服務(wù)進行交互,或者需要通過進程間通信 (IPC) 向其他應(yīng)用公開某些應(yīng)用功能越驻,則應(yīng)創(chuàng)建綁定服務(wù)汁政。
要創(chuàng)建綁定服務(wù),必須實現(xiàn) onBind() 回調(diào)方法以返回 IBinder缀旁,用于定義與服務(wù)通信的接口记劈。然后,其他應(yīng)用組件可以調(diào)用 bindService() 來檢索該接口并巍,并開始對服務(wù)調(diào)用方法目木。服務(wù)只用于與其綁定的應(yīng)用組件,因此如果沒有組件綁定到服務(wù)懊渡,則系統(tǒng)會銷毀服務(wù)(您不必按通過 onStartCommand() 啟動的服務(wù)那樣來停止綁定服務(wù))刽射。
要創(chuàng)建綁定服務(wù),首先必須定義指定客戶端如何與服務(wù)通信的接口剃执。 服務(wù)與客戶端之間的這個接口必須是 IBinder 的實現(xiàn)誓禁,并且服務(wù)必須從 onBind() 回調(diào)方法返回它。一旦客戶端收到 IBinder肾档,即可開始通過該接口與服務(wù)進行交互摹恰。
多個客戶端可以同時綁定到服務(wù)辫继。客戶端完成與服務(wù)的交互后俗慈,會調(diào)用 unbindService() 取消綁定姑宽。一旦沒有客戶端綁定到該服務(wù),系統(tǒng)就會銷毀它闺阱。
四炮车、在前臺運行服務(wù)
前臺服務(wù)被認(rèn)為是用戶主動意識到的一種服務(wù),因此在內(nèi)存不足時馏颂,系統(tǒng)也不會考慮將其終止示血。 前臺服務(wù)必須為狀態(tài)欄提供通知,放在“正在進行”標(biāo)題下方救拉,這意味著除非服務(wù)停止或從前臺移除难审,否則不能清除通知。
例如亿絮,應(yīng)該將通過服務(wù)播放音樂的音樂播放器設(shè)置為在前臺運行告喊,這是因為用戶明確意識到其操作。 狀態(tài)欄中的通知可能表示正在播放的歌曲派昧,并允許用戶啟動 Activity 來與音樂播放器進行交互黔姜。
要請求讓服務(wù)運行于前臺,請調(diào)用 startForeground()蒂萎。此方法采用兩個參數(shù):唯一標(biāo)識通知的整型數(shù)和狀態(tài)欄的 Notification秆吵。例如:
Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text),
System.currentTimeMillis());
Intent notificationIntent = new Intent(this, ExampleActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
notification.setLatestEventInfo(this, getText(R.string.notification_title),
getText(R.string.notification_message), pendingIntent);
startForeground(ONGOING_NOTIFICATION_ID, notification);
注意:提供給 startForeground() 的整型 ID 不得為 0。
要從前臺移除服務(wù)五慈,請調(diào)用 stopForeground()纳寂。此方法采用一個布爾值,指示是否也移除狀態(tài)欄通知泻拦。 此方法不會停止服務(wù)毙芜。 但是,如果您在服務(wù)正在前臺運行時將其停止争拐,則通知也會被移除腋粥。
五、IntentService
這是 Service 的子類架曹,它使用工作線程逐一處理所有啟動請求隘冲。如果您不要求服務(wù)同時處理多個請求,這是最好的選擇绑雄。 您只需實現(xiàn) onHandleIntent() 方法即可展辞,該方法會接收每個啟動請求的 Intent呻此,使您能夠執(zhí)行后臺工作。
由于大多數(shù)啟動服務(wù)都不必同時處理多個請求(實際上题涨,這種多線程情況可能很危險)霜定,因此使用 IntentService 類實現(xiàn)服務(wù)也許是最好的選擇肺缕。
IntentService 執(zhí)行以下操作:
創(chuàng)建默認(rèn)的工作線程扬卷,用于在應(yīng)用的主線程外執(zhí)行傳遞給 onStartCommand() 的所有 Intent扩然。
創(chuàng)建工作隊列掸犬,用于將 Intent 逐一傳遞給 onHandleIntent()實現(xiàn)珊楼,這樣您就永遠不必擔(dān)心多線程問題通殃。在處理完所有啟動請求后停止服務(wù),因此您永遠不必調(diào)用 stopSelf()厕宗。提供 onBind() 的默認(rèn)實現(xiàn)(返回 null)画舌。提供 onStartCommand() 的默認(rèn)實現(xiàn),可將 Intent 依次發(fā)送到工作隊列和 onHandleIntent() 實現(xiàn)已慢。
綜上所述曲聂,您只需實現(xiàn) onHandleIntent() 來完成客戶端提供的工作即可。(不過佑惠,您還需要為服務(wù)提供小型構(gòu)造函數(shù)朋腋。)
以下是 IntentService 的實現(xiàn)示例:
public class HelloIntentService extends IntentService {
/**
* A constructor is required, and must call the super IntentService(String)
* constructor with a name for the worker thread.
*/
public HelloIntentService() {
super("HelloIntentService");
}
/**
* The IntentService calls this method from the default worker thread with
* the intent that started the service. When this method returns, IntentService
* stops the service, as appropriate.
*/
@Override
protected void onHandleIntent(Intent intent) {
// Normally we would do some work here, like download a file.
// For our sample, we just sleep for 5 seconds.
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// Restore interrupt status.
Thread.currentThread().interrupt();
}
}
}
六、創(chuàng)建服務(wù)
您必須創(chuàng)建 Service 的子類(或使用它的一個現(xiàn)有子類)膜楷。在實現(xiàn)中旭咽,您需要重寫一些回調(diào)方法,以處理服務(wù)生命周期的某些關(guān)鍵方面并提供一種機制將組件綁定到服務(wù)(如適用)赌厅。 應(yīng)重寫的最重要的回調(diào)方法包括:
onStartCommand()
當(dāng)另一個組件(如 Activity)通過調(diào)用 startService() 請求啟動服務(wù)時穷绵,系統(tǒng)將調(diào)用此方法。一旦執(zhí)行此方法特愿,服務(wù)即會啟動并可在后臺無限期運行仲墨。 如果您實現(xiàn)此方法,則在服務(wù)工作完成后洽议,需要由您通過調(diào)用 stopSelf() 或 stopService() 來停止服務(wù)宗收。(如果您只想提供綁定,則無需實現(xiàn)此方法亚兄。)
onBind()
當(dāng)另一個組件想通過調(diào)用 bindService() 與服務(wù)綁定(例如執(zhí)行 RPC)時混稽,系統(tǒng)將調(diào)用此方法。在此方法的實現(xiàn)中审胚,您必須通過返回 IBinder 提供一個接口匈勋,供客戶端用來與服務(wù)進行通信。請務(wù)必實現(xiàn)此方法膳叨,但如果您并不希望允許綁定洽洁,則應(yīng)返回 null。
onCreate()
首次創(chuàng)建服務(wù)時菲嘴,系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設(shè)置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)饿自。如果服務(wù)已在運行汰翠,則不會調(diào)用此方法。
onDestroy()
當(dāng)服務(wù)不再使用且將被銷毀時昭雌,系統(tǒng)將調(diào)用此方法复唤。服務(wù)應(yīng)該實現(xiàn)此方法來清理所有資源,如線程烛卧、注冊的偵聽器佛纫、接收器等。 這是服務(wù)接收的最后一個調(diào)用总放。
如果組件通過調(diào)用 startService() 啟動服務(wù)(這會導(dǎo)致對 onStartCommand() 的調(diào)用)呈宇,則服務(wù)將一直運行,直到服務(wù)使用 stopSelf() 自行停止運行局雄,或由其他組件通過調(diào)用 stopService() 停止它為止甥啄。
如果組件是通過調(diào)用 bindService() 來創(chuàng)建服務(wù)(且未調(diào)用 onStartCommand(),則服務(wù)只會在該組件與其綁定時運行炬搭。一旦該服務(wù)與所有客戶端之間的綁定全部取消型豁,系統(tǒng)便會銷毀它。
僅當(dāng)內(nèi)存過低且必須回收系統(tǒng)資源以供具有用戶焦點的 Activity 使用時尚蝌,Android 系統(tǒng)才會強制停止服務(wù)迎变。如果將服務(wù)綁定到具有用戶焦點的 Activity,則它不太可能會終止飘言;如果將服務(wù)聲明為在前臺運行(稍后討論)衣形,則它幾乎永遠不會終止∽撕瑁或者谆吴,如果服務(wù)已啟動并要長時間運行,則系統(tǒng)會隨著時間的推移降低服務(wù)在后臺任務(wù)列表中的位置苛预,而服務(wù)也將隨之變得非常容易被終止句狼;如果服務(wù)是啟動服務(wù),則您必須將其設(shè)計為能夠妥善處理系統(tǒng)對它的重啟热某。 如果系統(tǒng)終止服務(wù)腻菇,那么一旦資源變得再次可用,系統(tǒng)便會重啟服務(wù)昔馋。
七筹吐、使用清單文件聲明服務(wù)
如同 Activity(以及其他組件)一樣,您必須在應(yīng)用的清單文件中聲明所有服務(wù)秘遏。
要聲明服務(wù)丘薛,請?zhí)砑?<service> 元素作為
<application> 元素的子元素。例如:
<manifest ... >
...
<application ... >
<service android:name=".ExampleService" />
...
</application>
</manifest>
您還可將其他屬性包括在 <service> 元素中邦危,以定義一些特性洋侨,如啟動服務(wù)及其運行所在進程所需的權(quán)限舍扰。android:name 屬性是唯一必需的屬性,用于指定服務(wù)的類名希坚。應(yīng)用一旦發(fā)布妥粟,即不應(yīng)更改此類名,如若不然吏够,可能會存在因依賴顯式 Intent 啟動或綁定服務(wù)而破壞代碼的風(fēng)險。
為了確保應(yīng)用的安全性滩报,請始終使用顯式 Intent 啟動或綁定 Service锅知,且不要為服務(wù)聲明 Intent 過濾器。 啟動哪個服務(wù)存在一定的不確定性脓钾,而如果對這種不確定性的考量非常有必要售睹,則可為服務(wù)提供 Intent 過濾器并從 Intent 中排除相應(yīng)的組件名稱,但隨后必須使用 setPackage() 方法設(shè)置 Intent 的軟件包可训,這樣可以充分消除目標(biāo)服務(wù)的不確定性昌妹。
此外,還可以通過添加 android:exported 屬性并將其設(shè)置為 "false"握截,確保服務(wù)僅適用于您的應(yīng)用飞崖。這可以有效阻止其他應(yīng)用啟動您的服務(wù),即便在使用顯式 Intent 時也如此谨胞。
八固歪、onStartCommand()的返回值
onStartCommand() 方法必須返回整型數(shù)。整型數(shù)是一個值胯努,用于描述系統(tǒng)應(yīng)該如何在服務(wù)終止的情況下繼續(xù)運行服務(wù)(如上所述牢裳,IntentService 的默認(rèn)實現(xiàn)將為您處理這種情況,不過您可以對其進行修改)叶沛。從 onStartCommand() 返回的值必須是以下常量之一:
START_NOT_STICKY</p>
如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù)蒲讯,則除非有掛起 Intent 要傳遞,否則系統(tǒng)不會重建服務(wù)灰署。這是最安全的選項判帮,可以避免在不必要時以及應(yīng)用能夠輕松重啟所有未完成的作業(yè)時運行服務(wù)。 </p>
START_STICKY</p>
如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù)溉箕,則會重建服務(wù)并調(diào)用 onStartCommand()脊另,但不會重新傳遞最后一個 Intent。相反约巷,除非有掛起 Intent 要啟動服務(wù)(在這種情況下偎痛,將傳遞這些 Intent ),否則系統(tǒng)會通過空 Intent 調(diào)用 onStartCommand()独郎。這適用于不執(zhí)行命令踩麦、但無限期運行并等待作業(yè)的媒體播放器(或類似服務(wù))枚赡。</p>
START_REDELIVER_INTENT</p>
如果系統(tǒng)在 onStartCommand() 返回后終止服務(wù),則會重建服務(wù)谓谦,并通過傳遞給服務(wù)的最后一個 Intent 調(diào)用 onStartCommand()贫橙。任何掛起 Intent 均依次傳遞。這適用于主動執(zhí)行應(yīng)該立即恢復(fù)的作業(yè)(例如下載文件)的服務(wù)反粥。
寫在最后: 本系列的文章旨在學(xué)習(xí)過程中的總結(jié)卢肃,如果對你也有幫助,榮幸之至才顿。