前言:本文所寫的是博主的個人見解,如有錯誤或者不恰當之處,歡迎私信博主,加以改正!原文鏈接,demo鏈接
Serviec(服務)簡述
什么是Service
Service 是一個可以在后臺執行長時間運行操作而不提供用戶界面的應用組件。Service 可以由其他應用組件啟動,即便用戶切換到其他應用,Service 仍將在后臺繼續運行。此外,組件可以綁定到 Service ,進行交互,甚至執行進程間通信(IPC)。例如,Service 可以處理網絡事務,播放音樂,執行文件讀寫或與內容提供程序交互,這一切都可以在后臺進行。-
服務的兩種基本形式
啟動
當應用組件(如 Activity )通過調用 startService() 啟動服務時,服務處于 “啟動” 狀態,一旦啟動,服務可以在后臺無限期運行,即使啟動服務的組件被銷毀了也不受影響。已經啟動的服務通常執行單一操作,而且不會講結果返回給調用方。例如,它可能通過網絡下載或者上傳文件。操作完成后,服務會自動停止運行。
綁定
當應用組件通過調用 bindService() 綁定到服務時,服務處于綁定狀態。綁定服務提供了一個客戶端-服務器( client-serve )接口,允許組件與服務進行交互,發送請求,獲取結果,甚至是利用進程間通信( IPC )跨進程執行這些操作。只有與另一個組件綁定時,綁定服務才會運行。多個組件可以綁定同個服務,但全部取消綁定后,該服務將會被銷毀。
雖然服務的形式有兩種,但服務可以同時以兩種方式運行,也就是說,它既可以是啟動服務(以無限期運行),也允許綁定。問題在于你是否實現一組回調方法: onStartCommand() (允許組件啟動服務) 和 onBind() (允許綁定服務)。
無論應用是否處于啟動狀態,綁定狀態,或是處于啟動并且綁定狀態,任何應用組件均可以像使用 Activity 那樣通過調用 Intent 來使用服務(即使服務來自另一個應用)。不過,你可以通過清單文件聲明服務為私有服務,阻止其他應用訪問。
注意:服務在其托管進程的主線程中運行,它不創建自己的線程,也不在單獨的進程中運行(除非另行指定)。這意味著,如果服務將執行任何CPU密集型工作或者阻止性操作(例如 MP3 播放或聯網),則應在服務內創建新的線程來完成這項工作,通過使用單獨的線程,可以降低發生ANR錯誤的風險,而應用的主線程仍可以繼續專注于運行用戶與 Activity 之間的交互。
認識 Service
要創建服務,必須創建 Service 的子類(或者使用它的一個現有子類)。需要重寫一些回調方法,以處理服務生命周期的有些關鍵方面,并提供一種機制將組件綁定到服務應重寫的最重要的回調方法包括:
onStartCommand()
當另一個組件(如 Activity )通過調用 startService() 請求啟動服務時,系統將調用此方法。一旦執行此方法,服務會啟動并可在后臺無限期運行。如果你實現了此方法,在服務工作完成后,需要調用 stopSelf() 或 stopService() 來停止服務(如果只是提供綁定則無需實現此方法)onBind()
當另一個組件調用 bindService() 與服務綁定時,系統將調用此方法。在此方法中必須返回 IBinder 提供一個接口,供客戶端與服務器進行通信。如果不希望允許綁定,則可以返回 null 。onCreate()
首次創建服務時,系統調用次方法來執行一次性程序(在調用 onStartCommand() 或 onBind() 之前)。如果服務已經運行則不會調用此方法。onDestory()
當服務不再使用且將被銷毀是,系統調用此方法。服務應該實現方法來清理所有資源,如線程、注冊的監聽器,接收器等。
如果組件調用 startService()啟動服務(會導致對 onStartCommand() 的調用),則服務將一直運行,知道服務使用 stopSelf() 自行停止運行或者其他組件調用 stopService() 停止它為止。
如果組件調用 bindService() 來創建服務(且未調用 onStartCommand() ),則服務只會在該組件與其綁定時運行,一旦服務與所有客戶端全部取消綁定時,系統會銷毀它。
僅當內存過低且系統必須回收資源供具有用戶焦點的 Activity 使用時,Android 系統才會強制停止服務。如果將服務綁定到具有用戶焦點的 Activity ,它被系統終止的可能性不大;如果將服務聲明為在前臺運行,則它幾乎永遠不會終止。如果服務已經啟動且要長時間運行,則系統會隨著時間推移降低服務在后臺列表中的位置,服務也將變得容易被終止;如果服務是啟動服務,則必須將其設計為能夠妥善處理系統對它的重啟。如果系統終止服務,那么一旦資源變得再次可用,系統便會重啟服務(取決于從 onStartCommand() )返回的值。
使用清單聲明服務
如同 Activity (或是其他組件)一樣,必須在應用的清單文件中聲明所有服務。
要聲明服務,清添加 <service>
元素作為 <application>
元素的子元素。例如
<manifest ... >
...
<application ... >
<service android:name=".DemoService" />
...
</application>
</manifest>
<service>
元素還可以包含其他屬性,定義一些特性,如啟動服務及運行所需要的權限。其中 android:name 屬性是唯一必須的屬性,用于指定服務的類名。應用一經發布,不可更改類名,不然會因依賴顯示 Intent 啟動或綁定服務而導致破壞代碼的風險。
為了確保應用的安全性,請使用顯示 Intent 或 綁定 Service , 而且不要為服務聲明 Intent 過濾器。啟動哪個服務存在不確定性,對這種不確定性的考量非常有必要,可以為服務提供 Intent 過濾器并從 Intent 中排除想應的組件名稱,但必須使用 setPackage() 方法設置 Intent 的軟件包, 這樣做可以消除目標服務的不確定性。
此外還可以通過添加 android:exporeted = "false"
,確保服務為應用私有,可以阻止其他應用啟動你的服務,同理,使用顯示 Intent 時也是如此。
啟動服務的創建
啟動服務由另一個組件通過調用 startService() 啟動,服務的 onStartCommand() 方法也將被調用。
服務啟動之后,其生命周期完全獨立,且可以在后臺無限期地運行,即使啟動服務的組件被銷毀了,該服務也不受影響。如果要結束該服務,可以調用 stopSelf() 自行停止運行,或者由另一個組件調用 stopService() 來停止。
應用組件(如 Activity )可以通過調用 startService() 方法且傳遞一個 Intent 對象(指定服務和其所有數據)來啟動服務。服務通過 onStartCommand() 方法來接收 Intent 對象。
例如,某個 Activity 需要保存一些數據到線上的數據庫中,這時它可以啟用一個協同服務,調用 startService() 并傳遞一個 Intent ,提供需要保存的數據。服務通過 onStartCommand() 接收 Intent ,連接到互聯網并執行數據庫事務,事務完成后,服務將自行停止運行且隨即被銷毀。
注意: 默認情況下,服務與服務聲明所在的應用處于同一進程,而且運行在主線程中。因此,如果是執行一些耗時操作,需要在服務內啟動新的線程,避免影響應用的性能。
你可以通過擴展兩個類來創建啟動服務:
Service
這是適用于所有服務的基類。默認情況下該服務將在應用的主線程中運行,你需要創建一個新的線程供服務工作,避免影響正在運行的所有 Activity 的性能。
IntentService
這個是 Service 的子類,它適用工作線程逐一處理所有啟動請求。如果不要求服務同時處理 請求,這毫無疑問是最好的選擇。只需要實現 onHandleIntent() 方法即可。該方法會接收每個啟動請求的 Intent ,使你能夠執行后臺工作。
下面演示如何使用其中任意一個類來實現服務。
-
擴展 IntentService 類
由于大多數啟動服務不用同時處理多個請求(這種多線程情況可能很危險),因此選擇 IntentService 類實現服務無疑是最好的。
IntentServic 執行以下的操作:
(1) 創建默認的工作線程,在主線程外執行傳遞給 onStartConmmand() 的所有 Intent。
(2) 創建工作隊列,將 Intent 逐一傳遞給 onHandleIntent() 實現,不用擔心多線程問題。
(3) 處理所有啟動請求后停止服務(不用手動調用 stopSelf() 來結束服務)
(4) 提供 onBind() 的默認實現(返回 null )。
(5) 提供 onStartCommand() 的默認實現,可以將 Intent 依次發送到工作隊列 和 onHandleIntent() 實現。綜上所述,只需要實現 onHandleIntent() 來完成客戶端提供的工作即可。(需要為服務提供構造函數)
下面是 IntentService 的實現示例:
public class DemoIntentService extends IntentService { /** * Creates an IntentService. Invoked by your subclass's constructor. * * @param name Used to name the worker thread, important only for debugging. */ public DemoIntentService(String name) { super(name); } @Override protected void onHandleIntent(@Nullable Intent intent) { //模擬耗時操作,線程沉睡3秒 try { Thread.sleep(3000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }
只需要一個構造函數和一個 onHandleIntent() 實現即可。
如果重寫其他回調方法(如 onCreate() 、onStartCommand() 或 onDestroy),要確保調用超類實現,讓 IntentService 能夠妥善處理工作線程的生命周期。
例如, onStartCommand() 必須返回默認實現(將 Intent 傳遞給 onHandleIntent() ):
@Override public int onStartCommand(@Nullable Intent intent, int flags, int startId) { Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); Log.i(TAG, "service starting"); return super.onStartCommand(intent, flags, startId); }
除了 onHandleIntent() 之外 ,無需調用的方法就是 onBind() (僅當服務允許綁定時,才需要實現該方法)
-
擴展服務類
如上部分所述,使用 IntentService 簡化了啟動服務的實現,如果要服務執行多線程(不是通過工作隊列處理啟動請求),則可以擴展 Service 類來處理每個 Intent 。
以下是 Service 類實現代碼示例,該類執行的工作與上面的 IntentService 示例相同。對每個啟動請求,它都使用工作線程執行作業,且每次僅處理一個請求。
public class DemoService extends Service {
private Looper mServiceLooper;
private ServiceHandle mServiceHandle;
@Override
public void onCreate() {
//啟動運行該服務的線程
HandlerThread thread = new HandlerThread("ServiceStartArguments", Process
.THREAD_PRIORITY_BACKGROUND);
thread.start();
//獲取HandlerThread的Looper并將其用于自己的Handler
mServiceLooper = thread.getLooper();
mServiceHandle = new ServiceHandle(mServiceLooper);
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
//每一個啟動請求,發送一個消息來啟動一個工作并提交開始Id
Message msg = mServiceHandle.obtainMessage();
msg.arg1 = startId;
mServiceHandle.sendMessage(msg);
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public void onDestroy() {
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show();
}
//從線程接收消息的處理程序
private final class ServiceHandle extends Handler {
public ServiceHandle(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
//模擬耗時操作,線程沉睡3秒
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
stopSelf(msg.arg1);
}
}
}
如上面的示例,與使用 IntentService 相比,這需顯得復雜一些。
但是,自己處理 onStartCommand() 的每個調用,因此可以同時執行多個請求。上面的示例沒有實現。如果你有需要,可以為每個請求創建一個新線程,然后立即運行這些線程(不是等待上一個請求完成)。
注意: onStartCommand() 方法必須返回整型數。 該返回值用于描述系統改如何在服務終止的情況下繼續運行服務,從 onStartCommand() 返回的值必須是以下常量注意之一:
- START_NOT_STICKY
如果系統在 onStartCommand() 返回后終止服務,除非有掛起的 Intent 要傳遞,否則系統不會重建服務。這是最安全的選項,可以避免在不必要時一級應用能夠輕松啟動所有未完成的作業時運行服務。
- START_STICKY
如果系統在 onStartCommand() 返回后終止服務,則會重建服務并調用 onStartCommand(),但不會重新傳遞最后一個 Intent 。相反,除非有掛起 Intent 要啟動服務(在這種情況下,傳遞這些 Intent ),否則系統會通過空 Intent 調用 onStartCommand() ,這個返回值在適用于不執行命令,但無限期運行并等待作業的媒體播放器(或類似服務)。
- START_REDELIVER_INTENT
如果系統在 onStartCommand() 返回后終止服務,則會重建服務,并通過傳遞給服務的最后一個 Intent 調用 onStartCommand() 。任何掛起 Intent 均依次傳遞。適用于主動執行應該立即恢復的作業(如下載文件)的服務。
3. 啟動服務
可以通過將 Intent (指定要啟動的服務)傳遞給 startService,從 Activity 或其他應用組件啟動服務。 Android 系統調用服務的 onStartCommand() 方法,并向其傳遞 Intent 。(請勿直接調用 onStartCommand() )
例如,Activity 可以結合使用顯式 Intent 與 startService(),啟動上文中的示例服務( DemoService ):
```java
Intent intent = new Intent(this,DemoService.class);
startService(intent);
startService() 方法將立即返回,且 Android 系統調用服務的 onStartCommand() 方法。如果服務尚未運行,則系統會先調用 onCreate() ,然后調用 onStartCommand() 。
如果服務未提供綁定,則使用 startService() 傳遞的 Intent 是應用組件與服務之間唯一的通信模式。但是,如果你希望服務返回結果,則啟動服務的客戶端可以為廣播創建一個 PendingIntent(使用 getBroadcast()),并通過啟動服務的 Intent 傳遞給服務,然后服務可以通過廣播來傳遞結果。
多個服務啟動請求會導致多次對服務的 onStartCommand() 進行相應的調用。但是要停止服務,只需要一個服務停止請求(使用 stopSelf() 或 stopService() )即可。
-
停止服務
啟動服務必須管理自己的生命周期。也就是說,除非系統必須回收內存資源,否則系統不會停止或銷毀服務,而且服務會在 onStartCommand() 返回后繼續運行。因此,服務必須通過調用 stopSelf() 自行停止運行,或者其他組件調用 stopService() 來停止。
一旦請求使用 stopSelf() 或者 stopService() 停止服務,系統就會盡快銷毀服務。
但是,如果服務同時處理多個 onStartCommand() 請求,則不應該在處理第一個啟動請求后停止服務,有可能你已經接收新的啟動請求(第一個請求結束時停止服務會終止第二個請求)。為了避免這個問題,可以使用 stopSelf( int ) 確保服務停止于最近的啟動請求。也就是,在調用 stopSelf( int ) 時,傳遞與停止請求的 ID 對應的啟動請求 ID (傳遞給 onStartCommand() 的 startId )。然后,在調用 stopSelf( int ) 之前服務收到了新的請求,ID 不匹配,服務也就不會停止。
注意: 為了避免浪費系統資源和小號電池電量,應用必須在工作完成后停止其服務。如有必要,其他組件可以通過調用 stopService 來停止服務,即使為服務啟用了綁定,一旦服務受到對 onStartCommand 的調用, 始終需要親自停止服務。
創建綁定服務
綁定服務允許應用通過調用 bindService() 與其綁定,以便創建長期連接(通常不允許組件通過調用 startService() 來啟動它)。
如需與 Activity 和其他應用組件中的服務進行交互,或者需要跨進程通信,則應創建綁定服務。
創建綁定服務,必須實現 onBind() 回調方法以返回 IBinder ,用于定義與服務通信的接口。然后其他應用組件可以調用 bindService() 來檢索該接口,并開始對服務調用方法。服務只用于與其綁定的應用組件,因此如果沒有組件綁定到服務,則系統會銷毀服務(不必通過 onStartCommand() 啟動的服務來停止綁定服務)。
要創建綁定服務,必須定義與指定客戶端與服務通信的接口。服務與客戶端之間的這個接口必須是 IBinder 的實現,且服務必須從 onBind() 回調方法返回它。一旦客戶端收到 IBinder ,即可開始通過該接口與服務器進行交互。
多個客戶端可以同時綁定到服務,客戶端完成與服務的交互后,會調用 unbindService() 取消綁定。一旦沒有客戶端綁定到該服務,系統就會銷毀它。
有多種方法實現綁定服務,實現方式比啟動服務更復雜,這里就不詳說了,后續會單獨的說明。
向用戶發送通知
服務一旦運行起來,即可使用 Toast 通知或狀態欄通知來通知用戶所發生的事情。
Toast 通知是指出現在當前窗口的表面、片刻隨即消失不見的消息,而狀態通知欄則在狀態欄中隨消息一起提供圖標,用戶可以選擇圖標來采取操作(例如啟動 Activity )。
通常,當某些后臺工作完成(錄入文件下載完成)且用戶現在可以對其進行操作時,狀態欄通知是最佳方法。當用戶從展開視圖中選定通知時,通知即可啟動 Activity (例如查看下載的文件)。
在前臺運行服務
前臺服務被認為是用戶主動意識到的一種服務,在內存不足時,系統也不會考慮將其終止。前臺服務必須為狀態欄提供通知,放在 “正在進行” 標題下方,除非服務停止或者從前臺溢出,否則不會清除通知。
例如,通過服務播放音樂的音樂播放器設置為在前臺運行,用戶能明確意識到其操作。狀態欄中的通知可能表示正在播放的歌曲,并允許用戶啟動 Activity 來與音樂播放器進行交互。
要請求讓服務運行于前臺,可以調用 startForeground() 。此方法采用兩個參數:唯一標識通知的整型數和通知欄的 Notification 。例如:
Notification.Builder builder = new Notification.Builder(this);
builder.setContentTitle("Notification title")
.setContentText("Notification describe")
.setSmallIcon(R.mipmap.ic_launcher);
Intent notificationIntent = new Intent(this,DemoActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);
builder.setContentIntent(pendingIntent);
Notification notification = builder.build();
startForeground(NOTIFICATION_ID,notification);
注意:提供給 startForegrond() 的整型 ID 不可以為0。
要從前臺移除服務,需要調用 stopForeground() 。次方法采用一個布爾值,指示是否移除狀態欄通知,此方法不會停止服務。但是,如果你的服務正在前臺運行時將其停止,則通知也會被移除。
管理服務生命周期
服務的生命周期比 Activity 的生周期要簡單多。但是密切關注如何創建和銷毀服務反而更重要,因為服務可以在用戶沒有意識到的情況下運行于后臺。
服務生命周期可以(從創建到銷毀)可以遵循兩條不同的路徑:
啟動服務
該服務在其他組件調用 startService() 時創建,然后無限期運行,且必須通過調用 stopSelf() 來自行停止運行。此外,其他組件也可以通過調用 stopService 來停止服務。服務停止后,系統將其銷毀。綁定服務
該服務在另一個組件(客戶端)調用 bindService() 時創建,然后客戶端通過 IBinder 接口與服務進行通信。客戶端可以調用 unbindService() 關閉連接。多個客戶端可以綁定到相同的服務,而且當所有綁定取消后,系統會銷毀該服務(服務不必自行停止)。
這兩條路徑并非完全獨立,也就是說,你可以綁定到已經使用 startService() 啟動的服務。例如,通過使用 Intent (標識要播放的音樂)調用 startService() 來啟動后臺音樂服務。隨后可能在用戶需要稍加控制播放器或獲取有關當前播放歌曲的信息時, Activity 可以通過調用 bindService() 綁定到服務,在這種情況下,除非所有客戶端取消綁定,否則 stopService() 或 stopSelf() 不會實際停止服務。
實現生命周期回調
與 Activity 類似,服務也擁有生命周期回調方法,你可以實現這些方法來監控服務狀態的變化,并適時執行工作。下面的例子展示了每種生命周期方法:
public class TestService extends Service {
int mStartMode; // 指示如果服務被殺死,該如何操作
IBinder mBinder; // 客戶端綁定接口
boolean mAllowRebind; // 指示是否應使用onRebind
@Override
public void onCreate() {
//服務正在創建中
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
//調用了startService(),服務正在啟動
return mStartMode;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//客戶端綁定到具有bindService()的服務
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
//所有客戶端都使用unbindService()取消綁定
return mAllowRebind;
}
@Override
public void onRebind(Intent intent) {
//客戶端綁定到具有bindService()的服務,
//onUnbind()已經被調用
}
@Override
public void onDestroy() {
//該服務已不再使用并被銷毀
}
}
注:與 Activity 生命周期回調方法不同,你不需要調用這些方法的超類實現。
![Service生命周期圖][1]
[1]: http://www.passershowe.com/img/service_lifecycle.png
左圖顯示了使用 startService() 所創建的服務的生命周期,右圖顯示了使用 bindService() 所創建的服務的生命周期。
通過實現這些方法,你可以監控這兩種服務生命周期:
服務的整個生命周期從調用 onCreate() 開始,到 onDestroy() 返回時結束。與 Activity 類似,服務也在 onCreate() 中完成初始設置,在 onDestroy() 中釋放所有剩余資源。例如音樂播放服務可以在 onCreate() 中創建用于播放音樂的線程,然后可以在 onDestroy() 中停止該線程。無論服務是通過 startService() 還是 bindService() 創建,都會為所有服務調用 onCreate() 和 onDestroy() 方法。
服務的有效生命周期從調用 onStartCommand() 或 onBind() 方法開始。每種方法均有 Intent 對象,該對象分別傳遞到 startService() 或 bindService() 。
對于啟動服務,有效生命周期與整個生命周期同時結束(即便是在 onStartCommand() 返回之后,服務仍然處于活動狀態)。對于綁定服務,有效生命周期在 onUnbind() 返回時結束。
注:盡管啟動服務是通過 stopSelf() 或 stopService() 來停止,但是該服務并無相應的回調(沒有 onStop 回調)。因此,除非服務綁定到客戶端,否則在服務停止時,系統會將其銷毀而 onDestroy() 是接收到的唯一回調。
盡管該圖分開介紹通過 startService() 創建的服務和通過 bindService() 創建的服務,但是記住一點,不管啟動方式如何,任何服務均有可能允許客戶端與其綁定。因此,最初使用 onStartCommand()(通過客戶端調用 startService())啟動的服務仍可接收對 onBind() 的調用(當客戶端調用 bindService() 時)。