Bound服務(wù)
英文原文:http://developer.android.com/guide/topics/fundamentals/bound-services.html
版本:Android 4.0 r1
譯者署名: 呆呆大蝦
譯者注:黃色底色為未決譯文
快速查看在本文中- 簡介
- 創(chuàng)建一個Bound服務(wù)
- 擴展Binder類
- 使用Messenger
- 綁定一個服務(wù)
- 管理Bound服務(wù)的生命周期
- Service
- ServiceConnection
- IBinder
- RemoteService
- LocalService
- Services
bound服務(wù)是客戶端-服務(wù)器模式的服務(wù)。bound服務(wù)允許組件(比如activity)對其進(jìn)行綁定、發(fā)送請求、接收響應(yīng)、甚至進(jìn)行進(jìn)程間通信(IPC)。bound服務(wù)一般只在為其它應(yīng)用程序組件服務(wù)期間才是存活的,而不會一直在后臺保持運行。
本文展示了如何創(chuàng)建一個bound服務(wù),包括如何從其它應(yīng)用程序組件綁定到該服務(wù)。不過,通常你還應(yīng)該參考 Services 文檔以獲取關(guān)于服務(wù)的更多信息,比如如何從服務(wù)中發(fā)送通知、如何將服務(wù)設(shè)置為前臺運行等等。
簡介
bound服務(wù)是 Service類的一種實現(xiàn),它允許其它應(yīng)用程序與其綁定并交互。為了讓服務(wù)支持綁定,你必須實現(xiàn) onBind() 回調(diào)方法。這個方法返回一個 IBinder對象,此對象定義了客戶端與服務(wù)進(jìn)行交互時所需的編程接口。
綁定到一個started服務(wù)
正如 Services 一文中所述,你可以創(chuàng)建一個同時支持started和bound的服務(wù)。也就是說,服務(wù)可以通過調(diào)用startService() 來啟動,這使它一直保持運行,同時它也允許客戶端通過調(diào)用 bindService() 來與之綁定。
如果你的服務(wù)確實可以是started和bound的,那么服務(wù)啟動后,系統(tǒng)將不會在所有客戶端解除綁定時銷毀它。取而代之的是,你必須通過調(diào)用stopSelf() 或 stopService() 顯式終止此服務(wù)。
雖然你通常應(yīng)該要實現(xiàn) onBind() 或 onStartCommand() 中的一個,但有時需要同時實現(xiàn)兩者。比如,音樂播放器的服務(wù)也許就需要同時實現(xiàn)后臺運行和支持綁定。這樣,activity就可以啟動服務(wù)來播放音樂,并且音樂會一直播放下去,即使用戶離開該應(yīng)用程序也沒關(guān)系,這個activity可以綁定播放服務(wù)來重新獲得播放控制權(quán)。
請確保已經(jīng)閱讀了 管理Bound服務(wù)的生命周期章節(jié),以獲取更多向started服務(wù)添加綁定時的服務(wù)生命周期的有關(guān)信息。
客戶端可以通過調(diào)用 bindService() 方法來綁定服務(wù)。在調(diào)用時,必須提供一個 ServiceConnection 的實現(xiàn)代碼,用于監(jiān)控與服務(wù)的聯(lián)接。 bindService()將會立即返回,沒有返回值。但是Android系統(tǒng)在創(chuàng)建客戶端與服務(wù)之間的聯(lián)接時,會調(diào)用 ServiceConnection 中的 onServiceConnected() 方法,傳遞一個 IBinder,客戶端將用它與服務(wù)進(jìn)行通信。
多個客戶端可以同時聯(lián)接到一個服務(wù)上。不過,只有在第一個客戶端綁定時,系統(tǒng)才會調(diào)用服務(wù)的 onBind() 方法來獲取 IBinder。然后,系統(tǒng)會向后續(xù)請求綁定的客戶端傳送這同一個 IBinder,而不再調(diào)用 onBind() 。
當(dāng)最后一個客戶端解除綁定后,系統(tǒng)會銷毀服務(wù)(除非服務(wù)同時是通過 startService() 啟動的)。
當(dāng)你實現(xiàn)自己的bound服務(wù)時,最重要的工作就是定義 onBind() 回調(diào)方法所返回的接口。定義服務(wù) IBinder接口的方式有好幾種,后續(xù)章節(jié)將會對每種技術(shù)進(jìn)行論述。
創(chuàng)建一個Bound服務(wù)
創(chuàng)建一個支持綁定的服務(wù)時,你必須提供一個 IBinder,用作客戶端和服務(wù)間進(jìn)行通信的編程接口。定義這類接口的方式有三種:
- 擴展Binder類
- 如果服務(wù)是你的應(yīng)用程序所私有的,并且與客戶端運行于同一個進(jìn)程中(通常都是如此),你應(yīng)該通過擴展 Binder類來創(chuàng)建你的接口,并從 onBind() 返回一個它的實例??蛻舳私邮赵?Binder對象并用它來直接訪問 Binder甚至 Service中可用的公共(public)方法。
如果你的服務(wù)只是為你自己的應(yīng)用程序執(zhí)行一些后臺工作,那這就是首選的技術(shù)方案。不用這種方式來創(chuàng)建接口的理由只有一個,就是服務(wù)要被其它應(yīng)用程序使用或者要跨多個進(jìn)程使用。
- 使用Messenger
- 如果你需要接口跨越多個進(jìn)程進(jìn)行工作,可以通過 Messenger來為服務(wù)創(chuàng)建接口。在這種方式下,服務(wù)定義一個響應(yīng)各類消息對象 Message的 Handler。此 Handler是 Messenger與客戶端共享同一個 IBinder的基礎(chǔ),它使得客戶端可以用消息對象 Message向服務(wù)發(fā)送指令。此外,客戶端還可以定義自己的 Message,以便服務(wù)能夠往回發(fā)送消息。
這是執(zhí)行進(jìn)程間通信(IPC)最為簡便的方式,因為 Messenger會把所有的請求放入一個獨立進(jìn)程中的隊列,這樣你就不一定非要把服務(wù)設(shè)計為線程安全的模式了。
- 使用AIDL
- Android接口定義語言AIDL(Android Interface Definition Language)完成以下的所有工作:將對象解析為操作系統(tǒng)可識別的原始形態(tài),并將它們跨進(jìn)程序列化(marshal)以完成IPC。 前一個使用Messenger的方式,實際上也是基于AIDL的,它用AIDL作為底層結(jié)構(gòu)。如上所述, Messenger將在一個單獨的進(jìn)程中創(chuàng)建一個包含了所有客戶端請求的隊列,這樣服務(wù)每次就只會收到一個請求??墒?,如果想讓你的服務(wù)能同時處理多個請求,那你就可以直接使用AIDL。這種情況下,你的服務(wù)必須擁有多線程處理能力,并且是以線程安全的方式編寫的。
要直接使用AIDL,你必須創(chuàng)建一個.aidl文件,其中定義了編程的接口。Android SDK 工具使用此文件來生成一個抽象類(abstractclass),其中實現(xiàn)了接口及對IPC的處理,然后你就可以在自己的服務(wù)中擴展該類。
注意:絕大多數(shù)應(yīng)用程序都不應(yīng)該用AIDL來創(chuàng)建bound服務(wù),因為這可能需要多線程處理能力并且會讓代碼變得更為復(fù)雜。因此,AIDL對絕大多數(shù)應(yīng)用程序都不適用,并且本文也不會討論如何在服務(wù)中使用它的內(nèi)容。如果你確信需要直接使用AIDL,那請參閱AIDL文檔。
擴展Binder類
如果你的服務(wù)只用于本地應(yīng)用程序并且不需要跨進(jìn)程工作,那你只要實現(xiàn)自己的 Binder類即可,這樣你的客戶端就能直接訪問服務(wù)中的公共方法了。
注意: 僅當(dāng)客戶端和服務(wù)位于同一個應(yīng)用程序和進(jìn)程中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應(yīng)用需要把一個activity綁定到它自己的后臺音樂播放服務(wù)上,采用這種方式就會很不錯。
以下是設(shè)置步驟:
- 在你的服務(wù)中,創(chuàng)建一個 Binder的實例,其中實現(xiàn)以下三者之一:
- 從回調(diào)方法 onBind() 中返回 Binder的該實例。
- 在客戶端中,在回調(diào)方法 onServiceConnected() 中接收 Binder并用所提供的方法對綁定的服務(wù)進(jìn)行調(diào)用。
注意:服務(wù)和客戶端之所以必須位于同一個應(yīng)用程序中,是為了讓客戶端能夠正確轉(zhuǎn)換(cast)返回的對象并調(diào)用對象的API。服務(wù)和客戶端也必須位于同一個進(jìn)程中,因為這種方式不能執(zhí)行任何跨進(jìn)程的序列化(marshalling)操作。
比如,以下是一個服務(wù)的示例,它通過實現(xiàn)一個 Binder來為客戶端訪問它內(nèi)部的方法提供支持:
public class LocalService extends Service {
// 給客戶端的Binder
private final IBinder mBinder = new LocalBinder();
// 生成隨機數(shù)
private final Random mGenerator = new Random();
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;
}
public int getRandomNumber() {
return mGenerator.nextInt(100);
}
}LocalBinder為客戶端提供了getService()方法,用于返回當(dāng)前LocalService的實例。這就讓客戶端可以調(diào)用服務(wù)中的公共方法。比如,客戶端可以調(diào)用服務(wù)中的getRandomNumber()。
以下是一個綁定到LocalService的activity,當(dāng)點擊按鈕時,它會調(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();
// 綁定到LocalService
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 與服務(wù)解除綁定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
public void onButtonClick(View v) {
if (mBound) {
// 調(diào)用LocalService中的方法。
// 不過,如果該調(diào)用會導(dǎo)致某些操作的掛起,那么調(diào)用應(yīng)該放入單獨的線程中進(jìn)行,
// 以免降低activity的性能。
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) {
// 我們已經(jīng)綁定到LocalService了,對IBinder進(jìn)行類型轉(zhuǎn)換(cast)并獲得LocalService對象的實例
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
![Android開發(fā)者指南-bound服務(wù)-BoundServices[原創(chuàng)譯文] upper bound](http://img.aihuau.com/images/02111102/02123355t01e4f44b09f19b53ed.png)
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}上述例子展示了客戶端如何利用 ServiceConnection 和 onServiceConnected() 回調(diào)方法綁定到服務(wù)。下一節(jié)將給出更多有關(guān)服務(wù)綁定過程的信息。
注意:上述例子并沒有明確地解除綁定,但所有的客戶端都應(yīng)該適時地解除綁定(比如activity暫停pause時)。
更多示例代碼,請參閱 ApiDemos 中的 LocalService.java類和 LocalServiceActivities.java類。
使用Messenger
與AIDL相比
當(dāng)你需要進(jìn)行IPC時,使用 Messenger要比用AIDL實現(xiàn)接口要容易些,因為 Messenger會把所有調(diào)用服務(wù)的請求放入一個隊列。而純粹的AIDL接口會把這些請求同時發(fā)送給服務(wù),這樣服務(wù)就必須要能夠多線程運行。
對于絕大多數(shù)應(yīng)用程序而言,服務(wù)沒有必要多線程運行,因此利用 Messenger可以讓服務(wù)一次只處理一個調(diào)用。如果 你的服務(wù)非要多線程運行,那你就應(yīng)該用 AIDL來定義接口。
如果你的服務(wù)需要與遠(yuǎn)程進(jìn)程進(jìn)行通信,那你可以使用一個 Messenger來提供服務(wù)的接口。這種技術(shù)能讓你無需使用AIDL就能進(jìn)行進(jìn)程間通信(IPC)。
以下概括了 Messenger的使用方法:
通過這種方式,客戶端不需要調(diào)用服務(wù)中的“方法”。取而代之的是,客戶端發(fā)送“消息”( Message對象),服務(wù)則接收位于 Handler中的這個消息。
以下是服務(wù)使用一個 Messenger做為接口的簡單例子:
public class MessengerService extends Service {
static final int MSG_SAY_HELLO = 1;
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);
}
}
}
final Messenger mMessenger = new Messenger(new IncomingHandler());
@Override
public IBinder onBind(Intent intent) {
Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
return mMessenger.getBinder();
}
}請注意 Handler中的 handleMessage() 方法,這里是服務(wù)接收輸入消息 Message的地方,也是根據(jù) what 數(shù)字來決定要執(zhí)行什么操作的地方。
客戶端要做的全部工作就是根據(jù)服務(wù)返回的 IBinder創(chuàng)建一個 Messenger,并用 send() 方法發(fā)送一個消息。例如,以下是一個activity示例,它綁定到上述服務(wù),并向服務(wù)發(fā)送MSG_SAY_HELLO消息:
public class ActivityMessenger extends Activity {
Messenger mService = null;
boolean mBound;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 與服務(wù)建立聯(lián)接后將會調(diào)用本方法,
// 給出用于和服務(wù)交互的對象。
// 我們將用一個Messenger來與服務(wù)進(jìn)行通信,
// 因此這里我們獲取到一個原始IBinder對象的客戶端實例。
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 當(dāng)與服務(wù)的聯(lián)接被意外中斷時——也就是說服務(wù)的進(jìn)程崩潰了,
// 將會調(diào)用本方法。
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
// 創(chuàng)建并向服務(wù)發(fā)送一個消息,用到了已約定的'what'值
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;
}
}
}請注意,上述例子中沒有給出服務(wù)是如何響應(yīng)客戶端的。如果你需要服務(wù)進(jìn)行響應(yīng),那你還需要在客戶端創(chuàng)建一個 Messenger。然后,當(dāng)客戶端接收到 onServiceConnected() 回調(diào)后,它再發(fā)送一個消息 Message給服務(wù),消息的 send() 方法中的 replyTo 參數(shù)里包含了客戶端的 Messenger。
在 MessengerService.java(服務(wù))和 MessengerServiceActivities.java(客戶端)例程中,你可以看到如何雙向發(fā)送消息的例子。
綁定一個服務(wù)
應(yīng)用程序組件(客戶端)可以通過調(diào)用 bindService() 來綁定服務(wù)。然后Android系統(tǒng)會調(diào)用服務(wù)的 onBind() 方法,返回一個用于和服務(wù)進(jìn)行交互的 IBinder。
綁定是異步進(jìn)行的。 bindService() 將立即返回,并不會向客戶端返回 IBinder。為了接收 IBinder,客戶端必須創(chuàng)建一個 ServiceConnection 的實例,并把它傳給 bindService() 。 ServiceConnection 包含了一個回調(diào)方法,系統(tǒng)將會調(diào)用該方法來傳遞客戶端所需的那個IBinder。
注意: 只有activity、服務(wù)和contentprovider才可以綁定到服務(wù)上——你不能從廣播接收器(broadcastreceiver)中綁定服務(wù)。
因此,要把客戶端綁定到服務(wù)上,你必須:
- 實現(xiàn) ServiceConnection 。
你的實現(xiàn)代碼必須重寫兩個回調(diào)方法:
- onServiceConnected()
- 系統(tǒng)調(diào)用該方法來傳遞服務(wù)的 onBind() 方法所返回的 IBinder。
- onServiceDisconnected()
- 當(dāng)與服務(wù)的聯(lián)接發(fā)生意外中斷時,比如服務(wù)崩潰或者被殺死時,Android系統(tǒng)將會調(diào)用該方法。客戶端解除綁定時,不會調(diào)用該方法。
- 調(diào)用 bindService() ,傳入已實現(xiàn)的 ServiceConnection 。
- 當(dāng)系統(tǒng)調(diào)用你的 onServiceConnected() 回調(diào)方法時,你可以利用接口中定義的方法開始對服務(wù)的調(diào)用。
- 要斷開與服務(wù)的聯(lián)接,請調(diào)用 unbindService() 。
當(dāng)客戶端被銷毀時,與服務(wù)的綁定也將解除。但與服務(wù)交互完畢后,或者你的activity進(jìn)入pause狀態(tài)時,你都應(yīng)該確保解除綁定,以便服務(wù)能夠在用完后及時關(guān)閉。(綁定和解除綁定的合適時機將在后續(xù)章節(jié)中繼續(xù)討論。)
例如,以下代碼段將客戶端與前面 擴展Binder類創(chuàng)建的服務(wù)聯(lián)接,而要做的全部工作就是把返回的 IBinder轉(zhuǎn)換(cast)為LocalService類,并獲取LocalService的實例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// 與服務(wù)的聯(lián)接建立之后將會調(diào)用
public void onServiceConnected(ComponentName className, IBinder service) {
// 因為我們已經(jīng)與明顯是運行于同一進(jìn)程中的服務(wù)建立了聯(lián)接,
// 我們就可以把它的IBinder轉(zhuǎn)換為一個實體類并直接訪問它。
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// 與服務(wù)的聯(lián)接意外中斷時將會調(diào)用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
利用這個 ServiceConnection ,客戶端就能夠把它傳入 bindService() 完成與服務(wù)的綁定。例如:
Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
其它注意事項
以下是有關(guān)綁定服務(wù)的一些重要注意事項:
注意: 你通常不應(yīng)該在activity的onResume() 和 onPause()中綁定和解除綁定,因為這兩個回調(diào)方法在每次切換生命周期狀態(tài)時都會發(fā)生,這時你應(yīng)該讓處理工作最少化。而且,如果應(yīng)用程序中有多個activity都綁定到同一個服務(wù)上,則在兩個activity間切換時都會發(fā)生狀態(tài)轉(zhuǎn)換,因為當(dāng)前activity解除綁定(在pause時)后,緊接著下一個activity又會進(jìn)行綁定(resume時),所以服務(wù)也許在銷毀后馬上就要重建。(這種activity狀態(tài)轉(zhuǎn)換、多個activity間的生命周期協(xié)作在 Activities 文檔中描述。)
更多展示綁定服務(wù)的示例代碼,請參閱 ApiDemos中的 RemoteService.java類。
管理Bound服務(wù)的生命周期
圖 1. started且允許綁定的服務(wù)的生命周期
一旦服務(wù)被所有客戶端解除綁定,則Android系統(tǒng)將會銷毀它(除非它同時又是用 onStartCommand()started)。因此,如果你的服務(wù)就是一個純粹的bound服務(wù),那你就不需要管理它的生命周期——Android系統(tǒng)會替你管理,根據(jù)是否還有客戶端對其綁定即可。
不過,如果你選擇實現(xiàn) onStartCommand()回調(diào)方法,那么你就必須顯式地終止服務(wù),因為此服務(wù)現(xiàn)在已經(jīng)被視為started了。這種情況下,無論是否還存在客戶端與其綁定,此服務(wù)都會運行下去,直至自行用stopSelf() 終止或由其它組件調(diào)用 stopService() 來終止。
此外,如果你的服務(wù)是started且允許被綁定,那么系統(tǒng)調(diào)用你的 onUnbind() 方法時,你可以選擇返回true。這樣作的結(jié)果就是,下次客戶端綁定時將會收到onRebind() 調(diào)用(而不是收到 onBind() 調(diào)用)。 onRebind() 返回void,但客戶端仍然能在它的 onServiceConnected() 回調(diào)方法中收到 IBinder。圖1展示了這種生命周期的運行邏輯。
關(guān)于started服務(wù)生命周期的更多信息,請參閱 Services文檔。
愛華網(wǎng)



