原文譯自:http://developer.android.com/guide/components/bound-services.html
1. 基礎知識
2. 創建綁定的服務
2.1 擴展Binder類
2.2 使用Messenger
3 .綁定到服務
4 .管理綁定的服務的生命週期
快速預覽
o 綁定的服務運行其他組件綁定到它,以便與其進行交互並執行進程間通信
o 一旦所有的客戶端解除綁定,綁定的服務則被銷燬,除非服務也是被啓動的
綁定的服務
綁定的服務是客戶端-服務器接口中的服務器。它允許組件(比如活動)綁定到服務,發送請求,接收反饋,並且甚至執行進程間通信(IPC)。綁定的服務通常僅在它服務其他應用組件時存在,而不會在後臺無限期地運行。
該文檔向你展示瞭如何創建綁定的服務,包括如何從其他應用組件綁定到它。不過你通常還應該參考服務文檔以瞭解額外的關於服務的信息,譬如如何從服務遞送通知,設置服務在前臺運行,等等。
1.基礎知識
綁定的服務是Service類的實現,它允許其他應用綁定到它並與其交互。爲給服務提供綁定,你必須實現onBind()回調方法。該方法返回一個IBinder對象,它定義了編程接口,客戶端可使用該接口同服務進行交互。
客戶端可以通過調用bindService()綁定到服務上。當服務提供綁定時,它必須提供一個ServiceConnection的實現,它控制着同服務的連接。bindService()方法立刻返回,而不返回任何值,但是當系統在客戶端和服務器之間創建連接時,它在ServiceConnection上調用onServiceConnected()來遞送IBinder,客戶端使用它去同服務通信。
多個客戶端可以同時綁定到服務上。然而,僅當第一個客戶端綁定時,系統才調用服務的onBind()方法來取出IBinder。然後系統傳遞同一個IBinder給任何額外的綁定到服務上的客戶端,而不會再次調用onBind()。
當最後一個客戶端從服務上解除綁定時,系統銷燬服務(除非服務還由startService()啓動)。
當實現綁定的服務時,最重要的部分是定義onBind()回調方法返回的接口。可以使用幾種不同的方式來定服務的IBinder接口,然後接下來的章節將討論每一種方法。
綁定到啓動的服務(started service)
如同在服務文檔中討論的一樣,你可以創建一個既是啓動的又是綁定的服務。也就是說, 服務可以由startService()啓動,它可以讓服務無限期地運行,同時也可以讓客戶端通過調用bindService()來綁定到服務上。
如果讓你的服務是啓動的又是綁定的,那麼當服務已經被啓動時,系統不會在所有客戶端解除綁定時銷燬服務。相反,你必須通過調用stopSelf()或stopService()來顯式地停止服務。
儘管你通常應該實現onBind()或onStartCommand(),但有時需要實現它們兩個。例如,音樂播放器可能認爲這樣是有用的以允許它的服務無限地運行同時也提供綁定。這樣一來,活動就可以啓動服務來播放音樂,並且即使用戶離開了應用,音樂也可以繼續播放。然後,當用戶返回到應用時,活動可以綁定到服務以再次控制播放。
更多關於在向啓動的服務添加綁定時服務的生命週期的信息, 請務必閱讀有關於管理綁定的服務的生命週期的章節。
2.創建綁定的服務
當創建提供綁定的服務時,必須提供一個IBinder,它提供了編程接口,客戶端可以用它來同服務交互。有三種方式可以定義該接口:
如果你的服務由你自己的應用私有,並且運行在與客戶端相同的進程內(通常如此),你應該通過擴展Binder類來創建你的接口,並從onBind()返回它的一個實例。客戶端接收Binder並使用它來直接訪問Binder實現內的或甚至是Service內的可用的公共方法。
如果你的服務僅在後臺爲你自己的應用工作,這是首選的方式。以這種方式不創建你的接口的唯一原因是因爲你的服務由其他應用使用或訪問單獨的進程。
如果你要求你的接口跨不同進程工作,可以使用Messenger爲服務創建一個接口。通過這種方式,服務需定義響應不同類型Message對象的Handler。這個Handler是Messenger的基礎,然後Messenger可以同客戶端共享一個IBinder,Handler允許客戶端使用Message對象向服務發送指令。此外,客戶端可以定義它自己的Messenger,因此服務可以把消息發送回來。
這是執行進程間通信(IPC)的最好方式,因爲Messenger把請求列入一個單獨的線程,所以你不必把你的服務設計成線程安全的。
使用 AIDL
AIDL (Android接口定義語言)執行的全部工作是把對象分解成操作系統可以理解的原語,並安排它們訪問進程以執行IPC。最初的技術是使用Messenger,它實際上是以AIDL爲基礎作爲它的底層結構。如上所述,Messenger在一個單獨線程裏創建一個所有客戶端的請求隊列,所以服務每次只接收一個請求。然而,假如你想讓你的服務同時去處理多個請求,那麼你可以直接使用AIDL。在這種情況下,你的服務必須是有多線程的能力並且被構建爲線程安全的。
爲了直接使用AIDL,你必須創建一個.aidl文件,它定義了編程接口。Android SDK工具使用這個文件生成一個抽象的類,這個類實現了接口並處理IPC,然後你可以在你的服務內部擴展這個類。
注意:大多數應用不應該使用AIDL來創建綁定的服務,因爲這或許需要多線程的能力以及可能導致更復雜的實現。正因如此,AIDL對大多數應用來說是不適合的,因此該文檔不會討論如何把它用於你的服務。如果你確定需要直接使用AIDL,請參閱AIDL文檔。
2.1 擴展Binder類
如果你的服務僅由本地應用使用,並且不需要跨進程工作,那麼你可以實現你自己的Binder類,它爲客戶端提供對服務內公共方法的直接訪問。
注意:這僅適用於當客戶端和服務是在相同的應用和進程中時,這是最常見的。例如,這很適合於需要把活動綁定到其自己的服務的音樂應用程序,該服務在後臺播放音樂。
下面是如何對其設置:
1. 在你的服務裏,創建一個Binder實例,它或許:
o 包含客戶端可以調用的公共方法
o 返回當前的Service實例,它擁有客戶端可以調用的公共方法
o 或者, 通過客戶端可以調用的公共方法返回由服務寄宿的其他類
2. 從onBind()回調方法返回Binder的這個實例。
3. 在客戶端,從onServiceConnected()回調方法接收Binder,然後使用這些提供的方法對綁定的服務進行調用。
注意:服務和客戶端必須處於相同應用中的原因是因爲客戶端由此可以轉型返回的對象並適當地調用它的APIs。服務和客戶端也必須處於相同的進程中,因爲這種技術不會執行任何跨進程地調度。
例如,這裏有一個服務,它通過一個的Binder實現來提供客戶端訪問其內部的方法:
public class LocalService extends Service {
// 給予客戶端的綁定
private final IBinder mBinder = new LocalBinder();
//隨機數產生器
private final Random mGenerator = new Random();
/**
* 用於客戶端綁定的類. 因爲我們知道服務通常
* 運行在和它的客戶端相同的進程內,因此我們不必處理IPC
*/
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()
。
這裏有一個活動,當點擊按鈕時,它綁定到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();
// 綁定到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;
}
}
/** 當點擊按鈕時被調用(按鈕在佈局文件裏通過android:onClick
* 屬性附加了這個方法)
*/
public void onButtonClick(View v) {
if (mBound) {
//從服務調用一個方法。
// 不過,如果這個調用可能導致掛起,那麼這個請求應該出現在一個獨立的線程裏以//避免使活動的執行慢下來
int num = mService.getRandomNumber();
Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show();
}
}
/** 爲服務綁定定義回調,並將其傳遞給bindService() */
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName className,
IBinder service) {
//我們已經綁定到 LocalService ,轉型IBinder 並得到LocalService實例
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
@Override
public void onServiceDisconnected(ComponentName arg0) {
mBound = false;
}
};
}
上面的例子展示了客戶端通過使用ServiceConnection實現以及onServiceConnected()回調是如何綁定到服務上的。接下來的章節提供了有關於綁定到服務這個過程的更多信息。
注意: 上面的例子沒有顯示地從服務解除綁定,但是所有客戶端在適當的時候應該會解除綁定(譬如當活動暫定時)。
更多實例代碼,參閱ApiDemos內的LocalService.java
類和LocalServiceActivities.java
類。
2.2 使用Messenger
如果你需要你的服務與遠程進程進行通信,那麼你可以使用Messenger來爲你的服務提供接口。這種技術允許你執行進程間通信 (IPC),而不必使用AIDL。
這裏是如何使用Messenger的一個總結:
· 服務實現Handler,它爲來自客戶端的每個調用接收回調。
· Handler被用來創建Messenger對象(它是Handler的一個引用)。
· 使用Messenger創建一個IBinder,服務將其從onBind()返回給客戶端。
· 客戶端使用IBinder初始化Messenger(它引用了服務的Handler),客戶端使用它向服務發送Message對象。
· 服務在它的Handler內 —— 具體地是在handleMessage()方法內,接收每個Message。
通過這種方式,沒有爲客戶端提供可在服務上調用的“方法”。作爲替代,客戶端遞送“message”(Message對象),服務在它的Handler內接收它。
這裏是一個簡單的使用了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());
/**
*當綁定到服務時,爲了向其發送消息,向我們的信使(messenger)返回一個接口
*/
@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消息:
public class ActivityMessenger extends Activity {
/** 與服務通信的信使*/
Messenger mService = null;
/** 指示我們是否已在服務上調用了綁定的標誌*/
boolean mBound;
/**
* 與服務的主接口進行交互的類
*/
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
// 當與服務的連接已建立時被調用,
// ,並給予我們可以使用的與服務進行交互的對象。
// 我們使用Messenger 與服務進行通信,
//, 所以,在這裏我們得到了來自原始Ibinder對象的客戶端的表示。
mService = new Messenger(service);
mBound = true;
}
public void onServiceDisconnected(ComponentName className) {
// 當與服務的連接意外地斷開時被調用 —— 就是說,它的進程奔潰了。
mService = null;
mBound = false;
}
};
public void sayHello(View v) {
if (!mBound) return;
//通過使用所支持的'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();
//綁定到服務
bindService(new Intent(this, MessengerService.class), mConnection,
Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
super.onStop();
// 從服務解除綁定
if (mBound) {
unbindService(mConnection);
mBound = false;
}
}
}
注意,這個例子沒有展示服務是如何能夠響應客戶端的。如果你想讓服務做出響應,那麼你還需要在客戶端創建一個Messenger。於是,當客戶端接收onServiceConnected()時,它向服務發送一個Message,它在其replyTo成員裏包含了客戶端的Messenger。(原文有誤:it sends a Message to the service that includes the client'sMessenger in thereplyTo parameter of thesend() method.)
在MessengerService.java
(服務)andMessengerServiceActivities.java
(客戶端)
的範例中可以看到一個如何提供兩種消息傳送方式的例子。
與AIDL的相比較
當執行IPC操作時,爲你的接口使用Messenger比通過AIDL實現它要簡單些,因爲Messenger排列了對服務的全部調用,然而,一個純粹的AIDL接口向服務發送同步請求,於是它必須處理多線程的問題。
對於絕大數應用來說,服務不必執行多線程操作,因此使用一個Messenger允許服務一次只處理一個請求。如果你的服務作爲多線程是很重要的,那麼你應該使用AIDL來定義你的接口。
3.綁定到服務
應用組件(客戶端)通過調用bindService()可以綁定到服務上。接着,Android系統調用服務的onBind()方法,它返回一個與服務交互的IBinder接口。
綁定是異步的。bindService()方法立刻返回,但不向客戶端返回IBinder。爲了得到IBinder,客戶端必須創建一個ServiceConnection的實例並把它傳遞給bindService()。ServiceConnection包括一個回調方法,系統調用它來回遞IBinder。
注意: 只有活動,服務和意圖提供者能綁定到服務上 — 你不能把廣播接收器綁定到服務上。
所以,爲了從客戶端綁定到服務上,你必須:
1. 實現ServiceConnection.
你的實現必須重寫兩個回調方法:
系統調用它來遞送由服務的onBind()方法返回的IBinder。
當同服務的連接意外丟失時Android系統調用這個方法,比如當服務已經崩潰或被殺死。當客戶端解除綁定時不會調用它。
2. 調用bindService(),並傳遞ServiceConnection實現。
3. 當系統調用你的onServiceConnected()回調方法時,你就可以使用由接口定義的方法開始對服務進行調用了。
4. 爲從服務斷開,調用unbindService()。
當客戶端銷燬時,它將從服務解除綁定,但是通常你應該在完成與服務的交互或你的活動暫停時解除綁定,以便服務在沒被使用時可以關閉。(關於綁定和解除綁定的恰當時機的更多討論在下面。)
例如,接下來的代碼段把客戶端連接到由上面擴展Binder類創建的服務上,所以它必須做的就是把返回的IBinder轉型爲LocalService
類,然後請求
LocalService
實例:
LocalService mService;
private ServiceConnection mConnection = new ServiceConnection() {
// 當建立了與服務的連接時被調用
public void onServiceConnected(ComponentName className, IBinder service) {
// 因爲我已經綁定到一個明確的運行於我們自己進程的服務上,
//所以我們可以把它的Ibinder轉型到一個具體的類並直接訪問它。
LocalBinder binder = (LocalBinder) service;
mService = binder.getService();
mBound = true;
}
// 當與服務的連接意外斷開時被調用
public void onServiceDisconnected(ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
mBound = false;
}
};
· bindService()的一個參數是顯式地指定了要綁定的服務的Intent(儘管意圖可以是隱式的)。
· 第二個參數是ServiceConnection對象。
·
第三個參數是一個指示綁定選項的標誌。它通常應該是BIND_AUTO_CREATE以在服務尚未存在時創建它。其他可能的值是BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND,或0代表沒有。
補充說明
下面是一些關於綁定到一個服務的重要說明:
· 通常,你應該捕獲DeadObjectException異常,它在連接斷開時被拋出。這是由遠程方法拋出的唯一異常。
· 對象是跨進程引用的。
· 通常,在客戶端生命週期啓動和銷燬時刻的期間,你應該把綁定和解除綁定組成對。例如:
o 在你的活動爲可見期間時,如果你僅需要與服務進行交互,那麼你應該onStart()時綁定並在onStart()時解除綁定。
o 如果你想讓你的活動接受響應,即使它在後臺被停止的期間,那麼你可以在onCreate()時綁定並在onDestroy()時解除綁定。注意,這意味着你的活動在其運行的整個時期(即使在後臺)都需要使用服務,所以如果服務運行在其他的進程裏,那麼你就增加了該進程的負擔並使其更有可能被系統殺死。
注意: 通常,你不應該在活動的onResume()和onPause()期間進行綁定和解除綁定,因爲在每一次生命週期過渡時就會調用這些回調,因此你應該把發生在這些轉變期間的處理保持到最低限度。此外,如果你的應用中的多個活動綁定到同一個服務上(原文意指多個活動可以綁定到同一個服務上,但並非同時綁定,否則下文不成了。),並且在這些活動中的兩個之間有一次過渡,則服務在下一個綁定前(恢復期間)可能會隨着當前活動的解除綁定(暫停期間)而被銷燬。 (活動如何調整它們生命週期的過渡在活動文檔內討論。)
更多展示如何綁定到服務上的示例代碼請參閱ApiDemos內的RemoteService.java
類。
4. 管理綁定的服務的生命週期
當服務從所有客戶端上解除綁定時,Android系統就銷燬它(除非它還被onStartCommand()啓動了)。因此,如果它是一個純粹的綁定的服務,那麼你不必管理你的服務的生命週期 —— Android系統會根據服務是否被綁定到了一些客戶端上來爲你管理其生命週期。
然而,如果你決定實現onStartCommand()回調方法,那麼你必須顯示地停止服務,因爲服務現在被視爲啓動的。在這種情況下,服務一直運行直到它通過stopSelf()停止其自己或者其他組件調用stopService(),而不管它是否被綁定到多少的客戶端上。
此外,如果你的服務是啓動的並接受綁定,那麼當系統調用你的onUnbind()方法時,你可以選擇性地true,如果你想要在客戶端下次綁定到服務上時收到onRebind() 調用的話(而不是onBind()調用)。onRebind() 返回void,但是客戶端依然會在它的onServiceConnected()回調內收到IBinder 。在下面,圖1說明了這類生命週期的邏輯。
圖1. 啓動的並接受綁定的服務生命週期
更多有關於啓動的服務的生命信息,請參閱服務文檔。
2012年12月27日 畢