前言
- Service 是一個可以在后臺執(zhí)行長時間運行操作而不提供用戶界面的應用組件。
- Service 可由其他應用組件啟動,而且即使用戶切換到其他應用,Service 仍將在后臺繼續(xù)運行。 此外,組件可以綁定到 Service,以與之進行交互,甚至是執(zhí)行進程間通信 (IPC)。
- Service 可以處理網(wǎng)絡事務、播放音樂,執(zhí)行文件 I/O 或與內(nèi)容提供程序交互,而所有這一切均可在后臺進行。
- Service 是運行在主線程里的,也就是說如果你在 Service 里編寫了非常耗時的代碼,程序必定會出現(xiàn)ANR的。
- 不要把后臺和子線程聯(lián)系在一起,這是兩個完全不同的概念。
- Android的后臺就是指,它的運行是完全不依賴UI的,即使Activity被銷毀,或者程序被關閉,只要進程還在,Service就可以繼續(xù)運行。比如說一些應用程序,始終需要與 Service 器之間始終保持著心跳連接,就可以使用Service來實現(xiàn)。對于耗時操作可以在 Service 中再創(chuàng)建一個子線程,然后在這里去處理耗時邏輯。
- 既然在 Service 里也要創(chuàng)建一個子線程,那為什么不直接在 Activity 里創(chuàng)建呢?這是因為Activity很難對 Thread 進行控制,當 Activity 被銷毀之后,就沒有任何其它的辦法可以再重新獲取到之前創(chuàng)建的子線程的實例。而且在一個Activity 中創(chuàng)建的子線程,另一個 Activity 無法對其進行操作。但是 Service 就不同了,所有的 Activity 都可以與Service 進行關聯(lián),然后可以很方便地操作其中的方法,即使 Activity 被銷毀了,之后只要重新與 Service 建立關聯(lián),就又能夠獲取到原有的 Service 中 Binder 的實例。因此,使用 Service 來處理后臺任務,Activity 就可以放心地
finish(),完全不需要擔心無法對后臺任務進行控制的情況。 - Thread 是程序執(zhí)行的最小單元,它是分配CPU的基本單位,可以用 Thread 來執(zhí)行一些異步的操作。
一、Service有兩種狀態(tài)
“啟動” 狀態(tài)、“綁定” 狀態(tài)。
startService()
當應用組件(如 Activity)通過調(diào)用 startService() 啟動 Service 時, Service 即處于“啟動”狀態(tài)。
一旦啟動, Service 即可在后臺無限期運行,即使啟動 Service 的組件已被銷毀也不受影響。
已啟動的 Service 通常是執(zhí)行單一操作,而且不會將結(jié)果返回給調(diào)用方。例如,它可能通過網(wǎng)絡下載或上傳文件。 操作完成后, Service 會自行停止運行。
如果 Service 同時處理多個 onStartCommand() 請求,則您不應在處理完一個啟動請求之后停止 Service ,因為您可能已經(jīng)收到了新的啟動請求(在第一個請求結(jié)束時停止 Service 會終止第二個請求)。為了避免這一問題,您可以使用 stopSelf(int) 確保 Service 停止請求始終基于最近的啟動請求。也就說,在調(diào)用 stopSelf(int) 時,傳遞與停止請求的 ID 對應的啟動請求的 ID(傳遞給 onStartCommand(int startId) 的 startId)。然后,如果在您能夠調(diào)用 stopSelf(int) 之前 Service 收到了新的啟動請求,ID 就不匹配, Service 也就不會停止。
bindService()
當應用組件通過調(diào)用 bindService() 綁定到 Service 時, Service 即處于“綁定”狀態(tài)。
綁定 Service 提供了一個 客戶端-Service 接口(ServiceConnection),允許組件與 Service 進行交互、發(fā)送請求、獲取結(jié)果,甚至是利用進程間通信 (IPC) 跨進程執(zhí)行這些操作。
僅當與另一個應用組件綁定時,綁定 Service 才會運行。 多個組件可以同時綁定到該 Service ,但全部取消綁定后,該 Service 即會被銷毀。
注意事項
為了確保應用的安全性,啟動 Service 時,請始終使用顯式 Intent,且不要為 Service 聲明 Intent 過濾器。使用隱式 Intent 啟動 Service 存在安全隱患,因為您無法確定哪些 Service 將響應 Intent,且用戶無法看到哪些 Service 已啟動。從 Android 5.0(API 級別 21)開始,如果使用隱式 Intent 調(diào)用 bindService(),系統(tǒng)會引發(fā)異常。
這兩種狀態(tài)并非完全獨立。也就是說,可以綁定到已經(jīng)使用 startService() 啟動的 Service 。例如,可以通過使用 Intent(標識要播放的音樂)調(diào)用 startService() 來啟動后臺音樂 Service 。隨后,可能在用戶需要稍加控制播放器或獲取有關當前播放歌曲的信息時,Activity 可以通過調(diào)用 bindService() 綁定到 Service 。在這種情況下,除非所有客戶端均取消綁定,否則 stopService() 或 stopSelf() 不會實際停止 Service 。
二、生命周期
如何使用Service
創(chuàng)建 Service 的子類(或使用它的一個現(xiàn)有子類,如 IntentService)。
在實現(xiàn)中,需要重寫一些回調(diào)方法,以處理 Service 生命周期的某些關鍵方面并提供一種機制將組件綁定到 Service (如適用)。
生命周期方法
| 生命周期方法 | 描述 |
|---|---|
onCreate() |
首次創(chuàng)建 Service 時,系統(tǒng)將調(diào)用此方法來執(zhí)行一次性設置程序(在調(diào)用 onStartCommand() 或 onBind() 之前)。如果 Service 已在運行,則不會調(diào)用此方法。 |
onStartCommand() |
當另一個組件(如 Activity)通過調(diào)用 startService() 請求啟動 Service 時,系統(tǒng)將調(diào)用此方法。一旦執(zhí)行此方法, Service 即會啟動并可在后臺無限期運行。 如果您實現(xiàn)此方法,則在 Service 工作完成后,需要由您通過調(diào)用 stopSelf() 或 stopService() 來停止 Service 。(如果您只想提供綁定,則無需實現(xiàn)此方法。) |
onBind() |
當另一個組件想通過調(diào)用 bindService() 與 Service 綁定(例如執(zhí)行 RPC)時,系統(tǒng)將調(diào)用此方法。在此方法的實現(xiàn)中,您必須通過返回 IBinder 提供一個接口,供客戶端用來與 Service 進行通信。請務必實現(xiàn)此方法,但如果您并不希望允許綁定,則應返回 null。 |
onRebind() |
在onUnbind()之后再次調(diào)用bindService()時,執(zhí)行該方法而不是執(zhí)行onBind()方法。 |
onUnbind() |
當最后一個綁定的組件解綁時或者調(diào)用unbindService()方法時,執(zhí)行該方法。注意該方法返回值默認為false,表示不允許重復綁定。如果返回false,再次執(zhí)行bindService()依然能夠成功,但是unbindService()就無效果了,所以如果想允許重復綁定一定要返回true
|
onDestroy() |
當 Service 不再使用且將被銷毀時,系統(tǒng)將調(diào)用此方法。 Service 應該實現(xiàn)此方法來清理所有資源,如線程、注冊的偵聽器、接收器等。 這是 Service 接收的最后一個調(diào)用。 |

onStartCommand()返回值
-
START_STICKY,如果 service 進程被 kill 掉,可恢復但不保留遞送的 intent 對象。kill 掉以后系統(tǒng)會嘗試重新創(chuàng)建 service。創(chuàng)建后會執(zhí)行該方法onStartCommand(Intent,int,int)。如果在此期間沒有任何啟動命令被傳遞到service,那么參數(shù) Intent 將為null。用startService(intent)傳遞命令參數(shù)。api5包括api5之后默認類型。 -
START_NOT_STICKY,使用這個返回值時,如果在執(zhí)行完onStartCommand()后, Service 被異常kill掉,系統(tǒng)不會自動重啟該 Service 。 -
START_REDELIVER_INTENT,重傳 Intent。使用這個返回值時,如果在執(zhí)行完onStartCommand()后, Service 被異常kill掉,系統(tǒng)會自動重啟該 Service ,并將Intent的值重新傳入。 -
START_STICKY_COMPATIBILITY,START_STICKY的兼容版本,但不保證 Service 被kill后一定能重啟。api5之前默認類型,現(xiàn)在基本沒用了
四、IntentService
Service
這是適用于所有 Service 的基類。擴展此類時,必須創(chuàng)建一個用于執(zhí)行所有 Service 工作的新線程,因為默認情況下, Service 將使用應用的主線程,這會降低應用正在運行的所有 Activity 的性能。
IntentService
這是 Service 的子類,它使用工作線程逐一處理所有啟動請求。如果您不要求 Service 同時處理多個請求,這是最好的選擇。 您只需實現(xiàn) onHandleIntent() 方法即可,該方法會接收每個啟動請求的 Intent,使您能夠執(zhí)行后臺工作。
IntentService已經(jīng)做了如下工作:
- 創(chuàng)建默認的工作線程,用于在應用的主線程外執(zhí)行傳遞給
onStartCommand()的所有Intent。 - 創(chuàng)建工作隊列,用于將
Intent逐一傳遞給onHandleIntent()實現(xiàn),這樣您就永遠不必擔心多線程問題。 - 在處理完所有啟動請求后停止 Service ,因此您永遠不必調(diào)用
stopSelf()。 - 提供
onBind()的默認實現(xiàn)(返回null)。 - 提供
onStartCommand()的默認實現(xiàn),可將Intent依次發(fā)送到工作隊列和onHandleIntent()實現(xiàn)。
五、Service 與 Activity交互
啟動服務狀態(tài)可以通過廣播的形式與Activity交互。
綁定服務通過 IBinder 接口方式交互。
要提供服務綁定,必須實現(xiàn) onBind() 回調(diào)方法。該方法返回的 IBinder 對象定義了客戶端用來與服務進行交互的編程接口??梢酝ㄟ^三種方法定義接口:
繼承 Binder 類
如果服務是供您的自有應用專用,并且在與客戶端相同的進程中運行(常見情況),則應通過繼承 Binder 類并從 onBind() 返回它的一個實例來創(chuàng)建接口??蛻舳耸盏?Binder 后,可利用它直接訪問 Binder 實現(xiàn)中乃至 Service 中可用的公共方法。如果服務只是您的自有應用的后臺工作線程,則優(yōu)先采用這種方法。 不以這種方式創(chuàng)建接口的唯一原因是,您的服務被其他應用或不同的進程占用。
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
/**
* Class used for the client Binder. Because we know this service always
* runs in the same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
LocalService getService() {
// Return this instance of LocalService so clients can call public methods
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
}
//客戶端關鍵代碼
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
// We've bound to LocalService, cast the IBinder and get LocalService instance
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
使用 Messenger
如需讓接口跨不同的進程工作,則可使用 Messenger 為服務創(chuàng)建接口。服務可以這種方式定義對應于不同類型 Message 對象的 Handler。此 Handler 是 Messenger 的基礎,后者隨后可與客戶端分享一個 IBinder,從而讓客戶端能利用 Message 對象向服務發(fā)送命令。此外,客戶端還可定義自有 Messenger,以便服務回傳消息。
這是執(zhí)行進程間通信 (IPC) 的最簡單方法,因為 Messenger 會在單一線程中創(chuàng)建包含所有請求的隊列,這樣您就不必對服務進行線程安全設計。
public class MessengerService extends Service {
/** Command to the service to display a message */
static final int MSG_SAY_HELLO = 1;
/**
* Handler of incoming messages from clients.
*/
class IncomingHandler extends Handler {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}
/**
* Target we publish for clients to send messages to IncomingHandler.
*/
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* When binding to the service, we return an interface to our messenger
* for sending messages to the service.
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
//客戶端關鍵代碼
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// This is called when the connection with the service has been
// established, giving us the object we can use to
// interact with the service. We are communicating with the
// service using a Messenger, so here we get a client-side
// representation of that from the raw IBinder object.
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// This is called when the connection with the service has been
// unexpectedly disconnected -- that is, its process crashed.
mService = null;
mBound = false;
}
};
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
使用 AIDL
AIDL(Android 接口定義語言)執(zhí)行所有將對象分解成原語的工作,操作系統(tǒng)可以識別這些原語并將它們編組到各進程中,以執(zhí)行 IPC。 之前采用 Messenger 的方法實際上是以 AIDL 作為其底層結(jié)構(gòu)。 如上所述,Messenger 會在單一線程中創(chuàng)建包含所有客戶端請求的隊列,以便服務一次接收一個請求。 不過,如果您想讓服務同時處理多個請求,則可直接使用 AIDL。 在此情況下,您的服務必須具備多線程處理能力,并采用線程安全式設計。
如需直接使用 AIDL,您必須創(chuàng)建一個定義編程接口的 .aidl 文件。Android SDK 工具利用該文件生成一個實現(xiàn)接口并處理 IPC 的抽象類,您隨后可在服務內(nèi)對其進行擴展。
注:大多數(shù)應用“都不會”使用 AIDL 來創(chuàng)建綁定服務,因為它可能要求具備多線程處理能力,并可能導致實現(xiàn)的復雜性增加。因此,AIDL 并不適合大多數(shù)應用,本文也不會闡述如何將其用于您的服務。如果您確定自己需要直接使用 AIDL,請參閱 AIDL 文檔。
參考 AIDL語法和使用詳解
六、前臺 Service
前臺服務被認為是用戶主動意識到的一種服務,因此在內(nèi)存不足時,系統(tǒng)也不會考慮將其終止。 前臺服務必須為狀態(tài)欄提供通知,放在“正在進行”標題下方,這意味著除非服務停止或從前臺移除,否則不能清除通知。
例如,應該將通過服務播放音樂的音樂播放器設置為在前臺運行,這是因為用戶明確意識到其操作。 狀態(tài)欄中的通知可能表示正在播放的歌曲,并允許用戶啟動 Activity 來與音樂播放器進行交互。
獲取 Service 存活狀態(tài)
public static boolean serviceIsRunning(Context context, Class<?extends Service> clazz){
//判斷 Service 是否已經(jīng)啟動,該方法也支持獨立進程的 Service 檢測,可以檢測到系統(tǒng)上所有正在運行的 Service 程序
ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
//得到運行的 Service 列表
List<ActivityManager.RunningServiceInfo> list = am.getRunningServices(Integer.MAX_VALUE);
//遍歷已啟動的 Service 列表
for (ActivityManager.RunningServiceInfo info : list) {
//獲取 Service 的ComponentName對象
ComponentName service = info.service;
String className = service.getClassName();
if (className.equals(clazz.getName())) {
return true;
}
}
return false;
}
設置前臺 Service
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.createNotificationChannel(new NotificationChannel(CHANNEL_ID, "MyService", NotificationManager.IMPORTANCE_DEFAULT));
}
Notification notification = new NotificationCompat.Builder(getApplicationContext(), CHANNEL_ID).build();
startForeground(ONGOING_NOTIFICATION_ID, notification);
附:參考
官方文檔
深入理解Android內(nèi)核設計思想.林學森
SDK源碼