目錄
- 0x10 介紹
- 0x20 知識準備
- 0x30 創建綁定服務
- 0x31 擴展 Binder 類
- 0x32 使用 Messenger
- 0x40 綁定到服務
- 0x41 附加說明
- 0x50 管理綁定服務的生命周期
可閱讀官方原文,我只是重讀舊文順便記錄,也改了一些翻譯
https://developer.android.google.cn/guide/components/bound-services.html
0x10 介紹
(被)綁定(的)服務是客戶端-服務器接口中的服務器。綁定服務可以讓組件(例如 Activity)綁定到服務,發送請求、接收響應,執行進程間通信(IPC)。綁定服務通常只在為其它應用組件服務時處于活動狀態,不會無限期在后臺運行。
0x20 知識準備
綁定服務是 Service 的一種工作方式,可以讓其它應用與其通過綁定實現交互。要提供綁定服務,必須實現 onBind() 回調方法。該方法返回的 IBinder 對象定義了客戶端與服務器進行交互的編程接口。
客戶端可以通過調用 bindService() 綁定到服務。調用時,客戶端必須提供 ServiceConnection 的實現,后者會監控與服務的連接。bindService() 方法 boolean 返回值表示被綁定的服務是否存在或者客戶端是否有權訪問。當客戶端與服務之間的連接創建完成, ServiceConnection 中的 onServiceConnected() 方法會被回調,向客戶端傳遞用來與服務通信的 IBinder。
多個客戶端可以同時連接一個服務,不過,只有在第一個客戶端綁定時,系統才會調用服務的 onBind() 方法來檢索 IBinder。系統隨后無需再次調用 onBind(),便可將同一個 IBinder 傳遞給任何其它綁定的客戶端。
當最后一個客戶端取消與服務的綁定時,系統會將服務銷毀(除非也通過 startService() 啟動了該服務)。
當實現綁定服務時,最重要的環節是定義 onBind() 回調方法返回的接口。可以通過集中不同的方法定義服務的 IBinder 接口,下面會逐一介紹。
0x30 創建綁定服務
創建提供綁定的服務時,必須提供 IBinder,用以提供客戶端與服務進行交互的編程接口。可以通過以下三種方式定義接口:
-
擴展 Binder 類
如果服務只是自有應用專用,并且與客戶端運行在同一個進程中,則可以通過擴展 Binder 類并從 onBind() 返回它的一個實例來創建接口。客戶端收到 Binder 后,可以通過它直接訪問 Binder 中實現或者 Service 中可用的公共方法。
如果服務知識自有應用的后臺工作線程,則優先采用這種方法。不以這種方式創建接口的唯一原因是,服務會被其它應用或不同的進程使用。 -
使用 Messenger
如果需要讓接口跨不同的進程工作,則可使用 Messenger 為服務創建接口。服務可以這種方式定義對應于不同類型 Message 對象的 Handler。此 Handler 是 Messenger 的基礎,后者隨后可以與客戶端分享一個 IBinder,從而讓客戶端能夠利用 Message 對象向服務發送命令。此外,客戶端還可以定義自有 Messenger,以便服務回傳消息。
這是執行進程間通信(IPC)最簡單方法,因為 Messenger 會在單一線程中創建包含所有請求的隊列,這樣就不必對服務進行線程安全設計。 -
使用 AIDL
AIDL(Android 接口定義語言)執行所有將對象分解成原語的工作,操作系統可以識別這些原語并將它們編組到各進程中,以執行 IPC。上面提到的采用 Messenger 的方法實際上是以 AIDL 作為其底層結構。如上所述,Messenger 會在單一線程中創建包含所有客戶端請求的隊列,以便服務一次接收一個請求。不過,如果想讓服務同時處理多個請求,則可以使用 AIDL。在此情況下,服務必須具有處理多線程的能力,并且采用線程安全式設計。
為了直接使用 AIDL,必須創建一個描述編程接口的 .aidl 文件。Android SDK 中工具利用該文件生成一個實現接口并處理 IPC 的抽象類,隨后可以在服務內對其進行擴展。
0x31 擴展 Binder 類
如果服務僅供本地應用使用,不需要跨進程工作,則可以實現自有 Binder 類,讓客戶端通過該類直接訪問服務中的公共方法。
注:此方法只有在客戶端和服務位于同一應用和進程這一常見的情況下才有效。例如,對于需要將 Activity 綁定到在后臺播放音樂的自有服務的音樂應用,此方法非常有效。
以下是實現方法:
- 在服務中,創建一個可以滿足下列任一要求的 Binder 實例:
- 包含客戶端可調用的公共方法
- 返回當前 Service 實例,其中包含客戶端可調用的公共方法
- 或返回由服務承載的其它類的實例,其中包含客戶端可調用的公共方法
- 從 onBind() 回調方法返回此 Binder 實例。
- 在客戶端中,從 onServiceConnected() 回調方法接收 Binder,并使用提供的方法調用綁定服務。
注:之所以要求服務和客戶端必須在同一應用內,是為了便于客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因為此方法不執行任何跨進程編組。
例子:
Service 部分代碼如下
public class LocalService extends Service {
// 返回給客戶端的 Binder
private final IBinder mBinder = new LocalBinder();
// 隨機數生成器
private final Random mGenerator = new Random();
/**
* 客戶端 Binder 對應的類
**/
public class LocalBinder extends Binder {
LocalService getService() {
// 返回 LocalService 的實例,客戶端可以調用其中的公共方法
return LocalService.this;
}
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder 為客戶端提供 getService() 方法,以檢索 LocalService 的當前實例。這樣,客戶端便可調用服務中的公共方法。例如,客戶端可調用服務中的 getRandomNumber()。
Activity 部分代碼如下
public class BindingActivity extends Activity {
LocalService mService;
boolean mBound = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// 綁定到 LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 解除綁定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public void onButtonClick(View v) {
if (mBound) {
// 調用 LocalService 中的方法
// 然而,如果調用被阻塞,會影響 activity 性能,所以應該在單獨的線程中調用
// occur in a separate thread to avoid slowing down the activity performance.
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** 定義服務綁定時的回調 */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
0x32 使用 Messenger
如需讓服務與遠程進程通信,則可使用 Messenger 為服務提供接口。利用此方法,無需使用 AIDL 便可執行進程間通信(IPC)。
實現步驟:
- 服務實現一個 Handler,由其接收來自客戶端的每個調用的回調
- Handler 用于創建 Messenger 對象(對 Handler 的引用)
- Messenger 創建一個 IBinder,服務通過 onBind() 使其返回客戶端
- 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,然后使用后者將 Message 對象發送給服務
- 服務在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每個 Message
這樣,客戶端并沒有調用服務的“方法”。而客戶端傳遞的“消息”(Message 對象)是服務在其 Handler 中接收的。
Service 例子:
public class MessengerService extends Service {
static final int MSG_SAY_HELLO = 1;
/**
* 處理客戶端發來的消息的 Handler
*/
class IncomingHandler extends Handler {
switch (msg.what) {
case MSG_SAY_HELLO:
Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
/** 客戶端用來給 IncomingHandler 發送消息的 Messenger */
final Messenger mMessenger = new Messenger(new IncomingHandler());
/**
* 當綁定到服務時,返回可以給服務發送消息的接口
*/
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}
Activity 例子:
public class ActivityMessenger extends Activity {
/** 用來與服務通信的 Messenger */
Messenger mService = null;
/** 是否已綁定到服務的標識 */
boolean mBound;
/**
* 與服務接口交互的類
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 當與服務間的連接建立時,這個方法會被回調,返回與服務交互用的對象。
// 我們使用 Messenger 與服務進行交互,
// 可以通過原始 IBinder 對象創建客戶端使用的 Messenger
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 當與服務間的連接意外斷開時,這個方法被調用。
// 例如進程崩潰
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 創建一個 Message 對象
Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0);
try {
mService.send(msg);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
@Override
protected void onStart() {
super.onStart();
// 綁定到服務
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 與服務解除綁定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
0x40 綁定到服務
應用組件(客戶端)可通過調用 bindService() 綁定到服務。Android 系統隨后調用服務的 onBind() 方法返回用于與服務交互的 IBinder。
綁定是異步的,bindService() 會立即返回一個 boolean 值,而不會返回 IBinder 給客戶端。要接收 IBinder,客戶端必須創建一個 ServiceConnection 實例,并將其傳遞給 bindService()。ServiceConnection 包括一個回調方法,系統通過調用它來傳遞 IBinder。
注:只有 Activity、Service 和 ContentProvider 可以綁定到服務,BroadcastReceiver 無法綁定到服務。
因此,要想從您的客戶端綁定到服務,必須:
- 實現 ServiceConnection,重寫兩個回調方法:
- onServiceConnected()
系統會調用該方法以傳遞服務的 onBind() 方法返回的 IBinder。 - onServiceDisconnected()
Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法。當客戶端取消綁定時,系統不會調用該方法。
- 調用 bindService(),傳遞 ServiceConnection 實現。
- 當系統調用 onServiceConnected() 回調方法時,可以使用接口定義的方法開始調用服務。
- 要斷開與服務的連接,調用 unbindService()。
如果應用在客戶端仍綁定到服務時銷毀客戶端,則銷毀會導致客戶端取消綁定。 更好的做法是在客戶端與服務交互完成后立即取消綁定客戶端。 這樣可以關閉空閑服務。如需了解有關綁定和取消綁定的適當時機的詳細信息,請參閱附加說明。
0x41 附加說明
以下是一些有關綁定到服務的重要說明:
- 應該始終捕獲 DeadObjectException 異常,它是在連接中斷時引發的。這是遠程方法引發的唯一異常。
- 對象的引用計數是跨進程的。
- 通常應該在客戶端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定。 例如:
- 如果只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop() 期間取消綁定。
- 如果希望 Activity 在后臺停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。請注意,這意味著 Activity 在其整個運行過程中(甚至包括后臺運行期間)都需要使用服務,因此如果服務位于其他進程內,那么當提高該進程的權重時,系統終止該進程的可能性會增加。
注:通常情況下,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,因為每一次生命周期轉換都會發生這些回調,您應該使發生在這些轉換期間的處理保持在最低水平。此外,如果您的應用內的多個 Activity 綁定到同一服務,并且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一個 Activity 綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷毀服務并重建服務。 (Activity 文檔中介紹了這種有關 Activity 如何協調其生命周期的 Activity 轉換。)
0x50 管理綁定服務的生命周期
當服務與所有客戶端之間的綁定全部取消時,Android 系統便會銷毀服務(除非還使用 onStartCommand() 啟動了該服務)。因此,如果服務是純粹的綁定服務,則無需對其生命周期進行管理 — Android 系統會根據它是否綁定到任何客戶端代管理。
不過,如果選擇實現 onStartCommand() 回調方法,則必須顯式停止服務,因為系統現在已將服務視為已啟動。在此情況下,服務將一直運行到其通過 stopSelf() 自行停止,或其他組件調用 stopService() 為止,無論其是否綁定到任何客戶端。
此外,如果服務已啟動并接受綁定,則當系統調用 onUnbind() 方法時,如果想在客戶端下一次綁定到服務時接收 onRebind() 調用,則可選擇返回 true
。onRebind() 返回空值,但客戶端仍在其 onServiceConnected() 回調中接收 IBinder 。下文圖 1 說明了這種生命周期的邏輯。