[文章內容來自Developers]
綁定服務是客戶端-服務器接口中的服務器。綁定服務可讓組件(例如 Activity)綁定到服務、發送請求、接收響應,甚至執行進程間通信 (IPC)。 綁定服務通常只在為其他應用組件服務時處于活動狀態,不會無限期在后臺運行。
基礎知識
綁定服務是 Service類的實現,可讓其他應用與其綁定和交互。要提供服務綁定,您必須實現 onBind()回調方法。該方法返回的 IBinder對象定義了客戶端用來與服務進行交互的編程接口。
客戶端可通過調用 bindService()綁定到服務。調用時,它必須提供 ServiceConnection的實現,后者會監控與服務的連接。bindService()方法會立即無值返回,但當 Android 系統創建客戶端與服務之間的連接時,會對 ServiceConnection調用 onServiceConnected(),向客戶端傳遞用來與服務通信的 IBinder。
多個客戶端可同時連接到一個服務。不過,只有在第一個客戶端綁定時,系統才會調用服務的onBind()方法來檢索 IBinder。系統隨后無需再次調用 onBind(),便可將同一 IBinder傳遞至任何其他綁定的客戶端。
當最后一個客戶端取消與服務的綁定時,系統會將服務銷毀(除非 startService()也啟動了該服務)。
當您實現綁定服務時,最重要的環節是定義您的 onBind() 回調方法返回的接口。您可以通過幾種不同的方法定義服務的 IBinder 接口,下文對這些方法逐一做了闡述。
創建綁定服務
創建提供綁定的服務時,您必須提供 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 的抽象類,您隨后可在服務內對其進行擴展。
注:大多數應用“都不會”使用 AIDL 來創建綁定服務,因為它可能要求具備多線程處理能力,并可能導致實現的復雜性增加。因此,AIDL 并不適合大多數應用,本文也不會闡述如何將其用于您的服務。如果您確定自己需要直接使用 AIDL,請參閱 AIDL 文檔。
綁定到已啟動服務
正如服務文檔中所述,您可以創建同時具有已啟動和綁定兩種狀態的服務。 也就是說,可通過調用startService()啟動該服務,讓服務無限期運行;此外,還可通過調用 bindService()使客戶端綁定到服務。
如果您確實允許服務同時具有已啟動和綁定狀態,則服務啟動后,系統“不會”在所有客戶端都取消綁定時銷毀服務。 為此,您必須通過調用stopSelf()或 stopService()顯式停止服務。
盡管您通常應該實現 onBind()或onStartCommand(),但有時需要同時實現這兩者。例如,音樂播放器可能發現讓其服務無限期運行并同時提供綁定很有用處。 這樣一來,Activity 便可啟動服務進行音樂播放,即使用戶離開應用,音樂播放也不會停止。 然后,當用戶返回應用時,Activity 可綁定到服務,重新獲得回放控制權。
請務必閱讀管理綁定服務的生命周期部分,詳細了解有關為已啟動服務添加綁定時該服務的生命周期信息。
擴展 Binder 類
如果您的服務僅供本地應用使用,不需要跨進程工作,則可以實現自有 Binder類,讓您的客戶端通過該類直接訪問服務中的公共方法。
注:此方法只有在客戶端和服務位于同一應用和進程內這一最常見的情況下方才有效。 例如,對于需要將 Activity 綁定到在后臺播放音樂的自有服務的音樂應用,此方法非常有效。
以下是具體的設置方法:
1.在您的服務中,創建一個可滿足下列任一要求的 Binder實例:包含客戶端可調用的公共方法返回當前 Service實例,其中包含客戶端可調用的公共方法或返回由服務承載的其他類的實例,其中包含客戶端可調用的公共方法
2.從 onBind()回調方法返回此 Binder實例。
3.在客戶端中,從 onServiceConnected()回調方法接收 Binder,并使用提供的方法調用綁定服務。
注:之所以要求服務和客戶端必須在同一應用內,是為了便于客戶端轉換返回的對象和正確調用其 API。服務和客戶端還必須在同一進程內,因為此方法不執行任何跨進程編組。
例如,以下這個服務可讓客戶端通過 Binder實現訪問服務中的方法:
public class LocalService extends Service {
// Binder given to clients
private final IBinder mBinder = new LocalBinder();
// Random number generator
private final Random mGenerator = new Random();
/**
* 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;
}
/** method for clients */
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}
LocalBinder為客戶端提供 getService()方法,以檢索 LocalService
的當前實例。這樣,客戶端便可調用服務中的公共方法。 例如,客戶端可調用服務中的 getRandomNumber()。
點擊按鈕時,以下這個 Activity 會綁定到 LocalService并調用 getRandomNumber():
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();
// Bind to LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
/** Called when a button is clicked (the button in the layout file attaches to
* this method with the android:onClick attribute)
*/
public void onButtonClick(View v) {
if (mBound) {
// Call a method from the LocalService.
// However, if this call were something that might hang, then this request should
// 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();
}
}
/** Defines callbacks for service binding, passed to bindService()
*/
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;
}
};
}
上例說明了客戶端如何使用 ServiceConnection的實現和 onServiceConnected() 回調綁定到服務。下文更詳細介紹了綁定到服務的過程。
注:在上例中,onStop() 方法將客戶端與服務取消綁定。 客戶端應在適當時機與服務取消綁定,如附加說明中所述。
如需查看更多示例代碼,請參見 ApiDemos 中的 LocalService.java
類和 LocalServiceActivities.java
類。
使用 Messenger
與 AIDL 比較當您需要執行 IPC 時,為您的接口用 Messenger要比使用 AIDL 實現它更加簡單,因為 Messenger 會將所有服務調用排入隊列,而純粹的 AIDL 接口會同時向服務發送多個請求,服務隨后必須應對多線程處理。
對于大多數應用,服務不需要執行多線程處理,因此使用 Messenger可讓服務一次處理一個調用。如果您的服務必須執行多線程處理,則應使用 AIDL來定義接口。
如需讓服務與遠程進程通信,則可使用 Messenger為您的服務提供接口。利用此方法,您無需使用 AIDL 便可執行進程間通信 (IPC)。
以下是 Messenger的使用方法摘要:
- 服務實現一個 Handler,由其接收來自客戶端的每個調用的回調
- Handler 用于創建 Messenger對象(對 Handler的引用)
- Messenger 創建一個 IBinder,服務通過 onBind()使其返回客戶端
- 客戶端使用 IBinder將 Messenger(引用服務的 Handler)實例化,然后使用后者將 Message對象發送給服務
- 服務在其 Handler 中(具體地講,是在 handleMessage()方法中)接收每個 Message。
這樣,客戶端并沒有調用服務的“方法”。而客戶端傳遞的“消息”(Message對象)是服務在其 Handler中接收的。
以下是一個使用 Messenger 接口的簡單服務示例:
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();
}
}
請注意,服務就是在 Handler的 handleMessage()方法中接收傳入的 Message,并根據 what成員決定下一步操作。
客戶端只需根據服務返回的 IBinder 創建一個 Messenger,然后利用 send()發送一條消息。例如,以下就是一個綁定到服務并向服務傳遞MSG_SAY_HELLO消息的簡單 Activity:
public class ActivityMessenger extends Activity {
/** Messenger for communicating with the service. */
Messenger mService = null;
/** Flag indicating whether we have called bind on the service. */
boolean mBound;
/**
* Class for interacting with the main interface of the service.
*/
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;
}
};
public void sayHello(View v) {
if (!mBound) return;
// Create and send a message to the service, using a supported 'what' value
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();
// Bind to the service
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// Unbind from the service
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
請注意,此示例并未說明服務如何對客戶端作出響應。如果您想讓服務作出響應,則還需要在客戶端中創建一個 Messenger。然后,當客戶端收到 onServiceConnected()回調時,會向服務發送一條 Message,并在其 send()方法的 replyTo參數中包含客戶端的 Messenger。
綁定到服務
應用組件(客戶端)可通過調用 bindService()綁定到服務。Android 系統隨后調用服務的 onBind()方法,該方法返回用于與服務交互的 IBinder。
綁定是異步的。bindService()會立即返回,“不會”使 IBinder 返回客戶端。要接收 IBinder,客戶端必須創建一個 ServiceConnection實例,并將其傳遞給 bindService()。ServiceConnection包括一個回調方法,系統通過調用它來傳遞 IBinder。
注:只有 Activity、服務和內容提供程序可以綁定到服務 — 您無法從廣播接收器綁定到服務。
因此,要想從您的客戶端綁定到服務,您必須:
1.實現 ServiceConnection。
您的實現必須重寫兩個回調方法:
- onServiceConnected()
系統會調用該方法以傳遞服務的onBind()方法返回的 IBinder。 - onServiceDisconnected()
Android 系統會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調用該方法。當客戶端取消綁定時,系統“不會”**調用該方法。
2.調用 [bindService(),傳遞 ServiceConnection 實現。
3.當系統調用您的 onServiceConnected() 回調方法時,您可以使用接口定義的方法開始調用服務。
4.要斷開與服務的連接,請調用 unbindService()。
如果應用在客戶端仍綁定到服務時銷毀客戶端,則銷毀會導致客戶端取消綁定。 更好的做法是在客戶端與服務交互完成后立即取消綁定客戶端。 這樣可以關閉空閑服務。
例如,以下代碼段通過擴展 Binder 類將客戶端與上面創建的服務相連,因此它只需將返回的 IBinder轉換為 LocalService類并請求 LocalService實例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// Called when the connection with the service is established
public void onServiceConnected(ComponentName
className, IBinder service) {
// Because we have bound to an explicit
// service that is running in our own process, we can
// cast its IBinder to a concrete class and directly access it.
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// Called when the connection with the service disconnects unexpectedly
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
客戶端可通過將此 ServiceConnection傳遞至 bindService()綁定到服務。例如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
bindService()的第一個參數是一個 Intent,用于顯式命名要綁定的服務(但 Intent 可能是隱式的)
第二個參數是 ServiceConnection對象
第三個參數是一個指示綁定選項的標志。它通常應該是 BIND_AUTO_CREATE,以便創建尚未激活的服務。其他可能的值為 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)。
附加說明
以下是一些有關綁定到服務的重要說明:
- 您應該始終捕獲 DeadObjectException異常,它們是在連接中斷時引發的。這是遠程方法引發的唯一異常。
- 對象是跨進程計數的引用。
您通常應該在客戶端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定。 例如:如果您只需要在 Activity 可見時與服務交互,則應在 onStart() 期間綁定,在 onStop()期間取消綁定。 - 如果您希望 Activity 在后臺停止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy()期間取消綁定。請注意,這意味著您的 Activity 在其整個運行過程中(甚至包括后臺運行期間)都需要使用服務,因此如果服務位于其他進程內,那么當您提高該進程的權重時,系統終止該進程的可能性會增加。
注:通常情況下,切勿在 Activity 的 onResume()和 onPause() 期間綁定和取消綁定,因為每一次生命周期轉換都會發生這些回調,您應該使發生在這些轉換期間的處理保持在最低水平。此外,如果您的應用內的多個 Activity 綁定到同一服務,并且其中兩個 Activity 之間發生了轉換,則如果當前 Activity 在下一個 Activity 綁定(恢復期間)之前取消綁定(暫停期間),系統可能會銷毀服務并重建服務。 (Activity文檔中介紹了這種有關 Activity 如何協調其生命周期的 Activity 轉換。)
如需查看更多顯示如何綁定到服務的示例代碼,請參閱 ApiDemos 中的 RemoteService.java
類。
管理綁定服務的生命周期
當服務與所有客戶端之間的綁定全部取消時,Android 系統便會銷毀服務(除非還使用 onStartCommand()啟動了該服務)。因此,如果您的服務是純粹的綁定服務,則無需對其生命周期進行管理 — Android 系統會根據它是否綁定到任何客戶端代您管理。
不過,如果您選擇實現 onStartCommand() 回調方法,則您必須顯式停止服務,因為系統現在已將服務視為已啟動。在此情況下,服務將一直運行到其通過 stopSelf()自行停止,或其他組件調用 stopService() 為止,無論其是否綁定到任何客戶端。
此外,如果您的服務已啟動并接受綁定,則當系統調用您的 onUnbind()方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind()調用,則可選擇返回 true。onRebind()返回空值,但客戶端仍在其 onServiceConnected()回調中接收 IBinder。下文圖 1 說明了這種生命周期的邏輯。
