[文章內(nèi)容來自Developers]
綁定服務是客戶端-服務器接口中的服務器驶冒。綁定服務可讓組件(例如 Activity)綁定到服務殊轴、發(fā)送請求嚎杨、接收響應蓄喇,甚至執(zhí)行進程間通信 (IPC)发侵。 綁定服務通常只在為其他應用組件服務時處于活動狀態(tài),不會無限期在后臺運行妆偏。
基礎知識
綁定服務是 Service類的實現(xiàn)刃鳄,可讓其他應用與其綁定和交互。要提供服務綁定钱骂,您必須實現(xiàn) onBind()回調(diào)方法叔锐。該方法返回的 IBinder對象定義了客戶端用來與服務進行交互的編程接口。
客戶端可通過調(diào)用 bindService()綁定到服務见秽。調(diào)用時愉烙,它必須提供 ServiceConnection的實現(xiàn),后者會監(jiān)控與服務的連接张吉。bindService()方法會立即無值返回齿梁,但當 Android 系統(tǒng)創(chuàng)建客戶端與服務之間的連接時,會對 ServiceConnection調(diào)用 onServiceConnected()肮蛹,向客戶端傳遞用來與服務通信的 IBinder勺择。
多個客戶端可同時連接到一個服務。不過伦忠,只有在第一個客戶端綁定時省核,系統(tǒng)才會調(diào)用服務的onBind()方法來檢索 IBinder。系統(tǒng)隨后無需再次調(diào)用 onBind()昆码,便可將同一 IBinder傳遞至任何其他綁定的客戶端气忠。
當最后一個客戶端取消與服務的綁定時,系統(tǒng)會將服務銷毀(除非 startService()也啟動了該服務)赋咽。
當您實現(xiàn)綁定服務時旧噪,最重要的環(huán)節(jié)是定義您的 onBind() 回調(diào)方法返回的接口。您可以通過幾種不同的方法定義服務的 IBinder 接口脓匿,下文對這些方法逐一做了闡述淘钟。
創(chuàng)建綁定服務
創(chuàng)建提供綁定的服務時,您必須提供 IBinder陪毡,用以提供客戶端用來與服務進行交互的編程接口米母。 您可以通過三種方法定義接口:
擴展 Binder 類
如果服務是供您的自有應用專用勾扭,并且在與客戶端相同的進程中運行(常見情況),則應通過擴展 Binder類并從 onBind()返回它的一個實例來創(chuàng)建接口铁瞒∶钌客戶端收到 Binder 后,可利用它直接訪問 Binder 實現(xiàn)中乃至 Service中可用的公共方法慧耍。如果服務只是您的自有應用的后臺工作線程身辨,則優(yōu)先采用這種方法。 不以這種方式創(chuàng)建接口的唯一原因是芍碧,您的服務被其他應用或不同的進程占用栅表。
使用 Messenger
如需讓接口跨不同的進程工作,則可使用 Messenger為服務創(chuàng)建接口师枣。服務可以這種方式定義對應于不同類型 Message 對象的 Handler。此 Handler是 Messenger的基礎萧落,后者隨后可與客戶端分享一個 IBinder践美,從而讓客戶端能利用 Message對象向服務發(fā)送命令。此外找岖,客戶端還可定義自有 Messenger陨倡,以便服務回傳消息。這是執(zhí)行進程間通信 (IPC) 的最簡單方法许布,因為 Messenger 會在單一線程中創(chuàng)建包含所有請求的隊列兴革,這樣您就不必對服務進行線程安全設計。
使用 AIDL
AIDL(Android 接口定義語言)執(zhí)行所有將對象分解成原語的工作蜜唾,操作系統(tǒng)可以識別這些原語并將它們編組到各進程中杂曲,以執(zhí)行 IPC。 之前采用 Messenger的方法實際上是以 AIDL 作為其底層結構袁余。 如上所述擎勘,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 文檔。
綁定到已啟動服務
正如服務文檔中所述荚虚,您可以創(chuàng)建同時具有已啟動和綁定兩種狀態(tài)的服務薛夜。 也就是說,可通過調(diào)用startService()啟動該服務版述,讓服務無限期運行梯澜;此外,還可通過調(diào)用 bindService()使客戶端綁定到服務渴析。
如果您確實允許服務同時具有已啟動和綁定狀態(tài)晚伙,則服務啟動后,系統(tǒng)“不會”在所有客戶端都取消綁定時銷毀服務俭茧。 為此咆疗,您必須通過調(diào)用stopSelf()或 stopService()顯式停止服務。
盡管您通常應該實現(xiàn) onBind()或onStartCommand()母债,但有時需要同時實現(xiàn)這兩者午磁。例如,音樂播放器可能發(fā)現(xiàn)讓其服務無限期運行并同時提供綁定很有用處毡们。 這樣一來迅皇,Activity 便可啟動服務進行音樂播放,即使用戶離開應用漏隐,音樂播放也不會停止喧半。 然后,當用戶返回應用時青责,Activity 可綁定到服務挺据,重新獲得回放控制權。
請務必閱讀管理綁定服務的生命周期部分脖隶,詳細了解有關為已啟動服務添加綁定時該服務的生命周期信息扁耐。
擴展 Binder 類
如果您的服務僅供本地應用使用,不需要跨進程工作产阱,則可以實現(xiàn)自有 Binder類婉称,讓您的客戶端通過該類直接訪問服務中的公共方法。
注:此方法只有在客戶端和服務位于同一應用和進程內(nèi)這一最常見的情況下方才有效。 例如王暗,對于需要將 Activity 綁定到在后臺播放音樂的自有服務的音樂應用悔据,此方法非常有效。
以下是具體的設置方法:
1.在您的服務中俗壹,創(chuàng)建一個可滿足下列任一要求的 Binder實例:包含客戶端可調(diào)用的公共方法返回當前 Service實例科汗,其中包含客戶端可調(diào)用的公共方法或返回由服務承載的其他類的實例,其中包含客戶端可調(diào)用的公共方法
2.從 onBind()回調(diào)方法返回此 Binder實例绷雏。
3.在客戶端中头滔,從 onServiceConnected()回調(diào)方法接收 Binder,并使用提供的方法調(diào)用綁定服務涎显。
注:之所以要求服務和客戶端必須在同一應用內(nèi)坤检,是為了便于客戶端轉(zhuǎn)換返回的對象和正確調(diào)用其 API。服務和客戶端還必須在同一進程內(nèi)期吓,因為此方法不執(zhí)行任何跨進程編組早歇。
例如,以下這個服務可讓客戶端通過 Binder實現(xiàn)訪問服務中的方法:
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
的當前實例缺前。這樣,客戶端便可調(diào)用服務中的公共方法悬襟。 例如,客戶端可調(diào)用服務中的 getRandomNumber()拯刁。
點擊按鈕時脊岳,以下這個 Activity 會綁定到 LocalService并調(diào)用 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的實現(xiàn)和 onServiceConnected() 回調(diào)綁定到服務。下文更詳細介紹了綁定到服務的過程垛玻。
注:在上例中割捅,onStop() 方法將客戶端與服務取消綁定惧互。 客戶端應在適當時機與服務取消綁定在辆,如附加說明中所述景醇。
如需查看更多示例代碼宵距,請參見 ApiDemos 中的 LocalService.java
類和 LocalServiceActivities.java
類谆构。
使用 Messenger
與 AIDL 比較當您需要執(zhí)行 IPC 時愁溜,為您的接口用 Messenger要比使用 AIDL 實現(xiàn)它更加簡單逼争,因為 Messenger 會將所有服務調(diào)用排入隊列搓蚪,而純粹的 AIDL 接口會同時向服務發(fā)送多個請求郭蕉,服務隨后必須應對多線程處理疼邀。
對于大多數(shù)應用,服務不需要執(zhí)行多線程處理召锈,因此使用 Messenger可讓服務一次處理一個調(diào)用旁振。如果您的服務必須執(zhí)行多線程處理,則應使用 AIDL來定義接口。
如需讓服務與遠程進程通信拐袜,則可使用 Messenger為您的服務提供接口吉嚣。利用此方法,您無需使用 AIDL 便可執(zhí)行進程間通信 (IPC)蹬铺。
以下是 Messenger的使用方法摘要:
- 服務實現(xiàn)一個 Handler尝哆,由其接收來自客戶端的每個調(diào)用的回調(diào)
- Handler 用于創(chuàng)建 Messenger對象(對 Handler的引用)
- Messenger 創(chuàng)建一個 IBinder,服務通過 onBind()使其返回客戶端
- 客戶端使用 IBinder將 Messenger(引用服務的 Handler)實例化丛塌,然后使用后者將 Message對象發(fā)送給服務
- 服務在其 Handler 中(具體地講较解,是在 handleMessage()方法中)接收每個 Message。
這樣赴邻,客戶端并沒有調(diào)用服務的“方法”印衔。而客戶端傳遞的“消息”(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奸焙,并根據(jù) what成員決定下一步操作。
客戶端只需根據(jù)服務返回的 IBinder 創(chuàng)建一個 Messenger彤敛,然后利用 send()發(fā)送一條消息与帆。例如,以下就是一個綁定到服務并向服務傳遞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;
}
}
}
請注意墨榄,此示例并未說明服務如何對客戶端作出響應玄糟。如果您想讓服務作出響應,則還需要在客戶端中創(chuàng)建一個 Messenger袄秩。然后阵翎,當客戶端收到 onServiceConnected()回調(diào)時,會向服務發(fā)送一條 Message之剧,并在其 send()方法的 replyTo參數(shù)中包含客戶端的 Messenger郭卫。
綁定到服務
應用組件(客戶端)可通過調(diào)用 bindService()綁定到服務。Android 系統(tǒng)隨后調(diào)用服務的 onBind()方法背稼,該方法返回用于與服務交互的 IBinder贰军。
綁定是異步的。bindService()會立即返回蟹肘,“不會”使 IBinder 返回客戶端词疼。要接收 IBinder,客戶端必須創(chuàng)建一個 ServiceConnection實例帘腹,并將其傳遞給 bindService()寒跳。ServiceConnection包括一個回調(diào)方法,系統(tǒng)通過調(diào)用它來傳遞 IBinder竹椒。
注:只有 Activity童太、服務和內(nèi)容提供程序可以綁定到服務 — 您無法從廣播接收器綁定到服務。
因此,要想從您的客戶端綁定到服務书释,您必須:
1.實現(xiàn) ServiceConnection翘贮。
您的實現(xiàn)必須重寫兩個回調(diào)方法:
- onServiceConnected()
系統(tǒng)會調(diào)用該方法以傳遞服務的onBind()方法返回的 IBinder。 - onServiceDisconnected()
Android 系統(tǒng)會在與服務的連接意外中斷時(例如當服務崩潰或被終止時)調(diào)用該方法爆惧。當客戶端取消綁定時狸页,系統(tǒng)“不會”**調(diào)用該方法。
2.調(diào)用 [bindService()扯再,傳遞 ServiceConnection 實現(xiàn)芍耘。
3.當系統(tǒng)調(diào)用您的 onServiceConnected() 回調(diào)方法時,您可以使用接口定義的方法開始調(diào)用服務熄阻。
4.要斷開與服務的連接斋竞,請調(diào)用 unbindService()。
如果應用在客戶端仍綁定到服務時銷毀客戶端秃殉,則銷毀會導致客戶端取消綁定坝初。 更好的做法是在客戶端與服務交互完成后立即取消綁定客戶端。 這樣可以關閉空閑服務钾军。
例如鳄袍,以下代碼段通過擴展 Binder 類將客戶端與上面創(chuàng)建的服務相連,因此它只需將返回的 IBinder轉(zhuǎn)換為 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()的第一個參數(shù)是一個 Intent拗小,用于顯式命名要綁定的服務(但 Intent 可能是隱式的)
第二個參數(shù)是 ServiceConnection對象
第三個參數(shù)是一個指示綁定選項的標志。它通常應該是 BIND_AUTO_CREATE樱哼,以便創(chuàng)建尚未激活的服務十籍。其他可能的值為 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)唇礁。
附加說明
以下是一些有關綁定到服務的重要說明:
- 您應該始終捕獲 DeadObjectException異常,它們是在連接中斷時引發(fā)的惨篱。這是遠程方法引發(fā)的唯一異常盏筐。
- 對象是跨進程計數(shù)的引用。
您通常應該在客戶端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 時刻期間配對綁定和取消綁定砸讳。 例如:如果您只需要在 Activity 可見時與服務交互琢融,則應在 onStart() 期間綁定,在 onStop()期間取消綁定簿寂。 - 如果您希望 Activity 在后臺停止運行狀態(tài)下仍可接收響應漾抬,則可在 onCreate() 期間綁定,在 onDestroy()期間取消綁定常遂。請注意纳令,這意味著您的 Activity 在其整個運行過程中(甚至包括后臺運行期間)都需要使用服務,因此如果服務位于其他進程內(nèi),那么當您提高該進程的權重時平绩,系統(tǒng)終止該進程的可能性會增加圈匆。
注:通常情況下,切勿在 Activity 的 onResume()和 onPause() 期間綁定和取消綁定捏雌,因為每一次生命周期轉(zhuǎn)換都會發(fā)生這些回調(diào)跃赚,您應該使發(fā)生在這些轉(zhuǎn)換期間的處理保持在最低水平。此外性湿,如果您的應用內(nèi)的多個 Activity 綁定到同一服務纬傲,并且其中兩個 Activity 之間發(fā)生了轉(zhuǎn)換,則如果當前 Activity 在下一個 Activity 綁定(恢復期間)之前取消綁定(暫停期間)肤频,系統(tǒng)可能會銷毀服務并重建服務叹括。 (Activity文檔中介紹了這種有關 Activity 如何協(xié)調(diào)其生命周期的 Activity 轉(zhuǎn)換。)
如需查看更多顯示如何綁定到服務的示例代碼着裹,請參閱 ApiDemos 中的 RemoteService.java
類领猾。
管理綁定服務的生命周期
當服務與所有客戶端之間的綁定全部取消時,Android 系統(tǒng)便會銷毀服務(除非還使用 onStartCommand()啟動了該服務)骇扇。因此摔竿,如果您的服務是純粹的綁定服務,則無需對其生命周期進行管理 — Android 系統(tǒng)會根據(jù)它是否綁定到任何客戶端代您管理少孝。
不過继低,如果您選擇實現(xiàn) onStartCommand() 回調(diào)方法,則您必須顯式停止服務稍走,因為系統(tǒng)現(xiàn)在已將服務視為已啟動袁翁。在此情況下,服務將一直運行到其通過 stopSelf()自行停止婿脸,或其他組件調(diào)用 stopService() 為止粱胜,無論其是否綁定到任何客戶端。
此外狐树,如果您的服務已啟動并接受綁定焙压,則當系統(tǒng)調(diào)用您的 onUnbind()方法時,如果您想在客戶端下一次綁定到服務時接收 onRebind()調(diào)用抑钟,則可選擇返回 true涯曲。onRebind()返回空值,但客戶端仍在其 onServiceConnected()回調(diào)中接收 IBinder在塔。下文圖 1 說明了這種生命周期的邏輯幻件。