Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> android基礎部分再學習---再談Service進程服務通信

android基礎部分再學習---再談Service進程服務通信

編輯:關於Android編程

Bound Services

快速查看 bound服務允許被其它控件綁定,以便與之交互並進行進程間通信 一旦所有的客戶端都解除了綁定,bound服務將被銷毀。除非該服務同時又是started類型的。 在本文中(參見目錄) 關鍵類 Service ServiceConnection IBinder 范例 RemoteService LocalService

bound服務是客戶端-服務器模式的服務。bound服務允許組件(比如activity)對其進行綁定、發送請求、接收響應、甚至進行進程間通信(IPC)。 bound服務一般只在為其它應用程序組件服務期間才是存活的,而不會一直在後台保持運行。

本文展示了如何創建一個bound服務,包括如何從其它應用程序組件綁定到該服務。不過,通常你還應該參考服務文檔以獲取關於服務的更多信息,比如如何從服務中發送通知、如何將服務設置為前台運行等等。

目錄


  • 1簡介
  • 2創建一個Bound服務
  • 2.1擴展Binder類
  • 2.2使用Messenger
  • 3綁定一個服務
  • 4管理Bound服務的生命周期

簡介


bound服務是Service類的一種實現,它允許其它應用程序與其綁定並交互。為了讓服務支持綁定,你必須實現onBind()回調方法。這個方法返回一個IBinder對象,此對象定義了客戶端與服務進行交互時所需的編程接口。

綁定到一個started服務

正如服務一文中所述,你可以創建一個同時支持started和bound的服務。也就是說,服務可以通過調用startService()來啟動,這使它一直保持運行,同時它也允許客戶端通過調用bindService()來與之綁定。

如果你的服務確實可以是started和bound的,那麼服務啟動後,系統將不會在所有客戶端解除綁定時銷毀它。取而代之的是,你必須通過調用stopSelf()或stopService()顯式終止此服務。

雖然你通常應該要實現onBind()onStartCommand()中的一個,但有時需要同時實現兩者。比如,音樂播放器的服務也許就需要同時實現後台運行和支持綁定。這樣,activity就可以啟動服務來播放音樂,並且音樂會一直播放下去,即使用戶離開該應用程序也沒關系,這個activity可以綁定播放服務來重新獲得播放控制權。

請確保已經閱讀了#管理Bound服務的生命周期章節,以獲取更多向started服務添加綁定時的服務生命周期的有關信息。

客戶端可以通過調用bindService()方法來綁定服務。在調用時,必須提供一個ServiceConnection的實現代碼,用於監控與服務的聯接。bindService()將會立即返回,沒有返回值。但是Android系統在創建客戶端與服務之間的聯接時,會調用ServiceConnection中的onServiceConnected()方法,傳遞一個IBinder,客戶端將用它與服務進行通信。

多個客戶端可以同時聯接到一個服務上。不過,只有在第一個客戶端綁定時,系統才會調用服務的onBind()方法來獲取IBinder。然後,系統會向後續請求綁定的客戶端傳送這同一個IBinder,而不再調用onBind()。

當最後一個客戶端解除綁定後,系統會銷毀服務(除非服務同時是通過startService()啟動的)。

當你實現自己的bound服務時,最重要的工作就是定義onBind()回調方法所返回的接口。定義服務IBinder接口的方式有好幾種,後續章節將會對每種技術進行論述。

創建一個Bound服務


創建一個支持綁定的服務時,你必須提供一個IBinder,用作客戶端和服務間進行通信的編程接口。定義這類接口的方式有三種:

擴展Binder類 如果服務是你的應用程序所私有的,並且與客戶端運行於同一個進程中(通常都是如此),你應該通過擴展Binder類來創建你的接口,並從onBind()返回一個它的實例。客戶端接收該Binder對象並用它來直接訪問Binder甚至Service中可用的公共(public)方法。 如果你的服務只是為你自己的應用程序執行一些後台工作,那這就是首選的技術方案。不用這種方式來創建接口的理由只有一個,就是服務要被其它應用程序使用或者要跨多個進程使用。 使用Messenger 如果你需要接口跨越多個進程進行工作,可以通過Messenger來為服務創建接口。在這種方式下,服務定義一個響應各類消息對象Message的Handler。此Handler是Messenger與客戶端共享同一個IBinder的基礎,它使得客戶端可以用消息對象Message向服務發送指令。此外,客戶端還可以定義自己的Message,以便服務能夠往回發送消息。 這是執行進程間通信(IPC)最為簡便的方式,因為Messenger會把所有的請求放入一個獨立進程中的隊列,這樣你就不一定非要把服務設計為線程安全的模式了。 使用AIDL Android接口定義語言AIDL(Android Interface Definition Language)完成以下的所有工作:將對象解析為操作系統可識別的原始形態,並將它們跨進程序列化(marshal)以完成IPC。前一個使用Messenger的方式,實際上也是基於AIDL的,它用AIDL作為底層結構。如上所述,Messenger將在一個單獨的進程中創建一個包含了所有客戶端請求的隊列,這樣服務每次就只會收到一個請求。可是,如果想讓你的服務能同時處理多個請求,那你就可以直接使用AIDL。這種情況下,你的服務必須擁有多線程處理能力,並且是以線程安全的方式編寫的。 要直接使用AIDL,你必須創建一個.aidl文件,其中定義了編程的接口。Android SDK 工具使用此文件來生成一個抽象類(abstract class),其中實現了接口及對IPC的處理,然後你就可以在自己的服務中擴展該類。

注意:絕大多數應用程序都不應該用AIDL來創建bound服務,因為這可能需要多線程處理能力並且會讓代碼變得更為復雜。 因此,AIDL對絕大多數應用程序都不適用,並且本文也不會討論如何在服務中使用它的內容。如果你確信需要直接使用AIDL,那請參閱AIDL文檔。

擴展Binder類

如果你的服務只用於本地應用程序並且不需要跨進程工作,那你只要實現自己的Binder類即可,這樣你的客戶端就能直接訪問服務中的公共方法了。

注意:僅當客戶端和服務位於同一個應用程序和進程中,這也是最常見的情況,這種方式才會有用。比如,一個音樂應用需要把一個activity綁定到它自己的後台音樂播放服務上,采用這種方式就會很不錯。

以下是設置步驟:

在你的服務中,創建一個Binder的實例,其中實現以下三者之一: 包含了可供客戶端調用的公共方法 返回當前Service實例,其中包含了可供客戶端調用的公共方法。 或者,返回內含服務類的其它類的一個實例,服務中包含了可供客戶端調用的公共方法。 從回調方法onBind()中返回Binder的該實例。 在客戶端中,在回調方法onServiceConnected()中接收Binder並用所提供的方法對綁定的服務進行調用。 注意:

服務和客戶端之所以必須位於同一個應用程序中,是為了讓客戶端能夠正確轉換(cast)返回的對象並調用對象的API。 服務和客戶端也必須位於同一個進程中,因為這種方式不能執行任何跨進程的序列化(marshalling)操作。

比如,以下是一個服務的示例,它通過實現一個Binder來為客戶端訪問它內部的方法提供支持:

publicclassLocalServiceextendsService{
// 給客戶端的Binder
privatefinalIBindermBinder=newLocalBinder();
// 生成隨機數
privatefinalRandommGenerator=newRandom();

/**
* 用於客戶端Binder的類。
* 因為知道本服務總是運行於與客戶端相同的進程中,我們就不需要用IPC進行處理。
*/
publicclassLocalBinderextendsBinder{
LocalServicegetService(){
// Return this instance of LocalService so clients can call public methods
returnLocalService.this;
}
}

@Override
publicIBinderonBind(Intentintent){
returnmBinder;
}

/** method for clients */
publicintgetRandomNumber(){
returnmGenerator.nextInt(100);
}
}

LocalBinder為客戶端提供了getService()方法,用於返回當前LocalService的實例。 這就讓客戶端可以調用服務中的公共方法。比如,客戶端可以調用服務中的getRandomNumber()。

以下是一個綁定到LocalService的activity,當點擊按鈕時,它會調用getRandomNumber():

publicclassBindingActivityextendsActivity{
LocalServicemService;
booleanmBound=false;

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}

@Override
protectedvoidonStart(){
super.onStart();
// 綁定到LocalService
Intentintent=newIntent(this,LocalService.class);
bindService(intent,mConnection,Context.BIND_AUTO_CREATE);
}

@Override
protectedvoidonStop(){
super.onStop();
// 與服務解除綁定
if(mBound){
unbindService(mConnection);
mBound=false;
}
}

/** 當按下按鈕時調用(該按鈕在layout文件中利用android:onClick屬性與本方法關聯 */
publicvoidonButtonClick(Viewv){
if(mBound){
// 調用LocalService中的方法。
// 不過,如果該調用會導致某些操作的掛起,那麼調用應該放入單獨的線程中進行,
// 以免降低activity的性能。
intnum=mService.getRandomNumber();
Toast.makeText(this,"number: "+num,Toast.LENGTH_SHORT).show();
}
}

/** 定義服務綁定時的回調方法,用於傳給bindService() */
privateServiceConnectionmConnection=newServiceConnection(){

@Override
publicvoidonServiceConnected(ComponentNameclassName,
IBinderservice){
// 我們已經綁定到LocalService了,對IBinder進行類型轉換(cast)並獲得LocalService對象的實例
LocalBinderbinder=(LocalBinder)service;
mService=binder.getService();
mBound=true;
}

@Override
publicvoidonServiceDisconnected(ComponentNamearg0){
mBound=false;
}
};
}

上述例子展示了客戶端如何利用ServiceConnection和onServiceConnected()回調方法綁定到服務。下一節將給出更多有關服務綁定過程的信息。

注意:

上述例子並沒有明確地解除綁定,但所有的客戶端都應該適時地解除綁定(比如activity暫停pause時)。

更多示例代碼,請參閱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中的每個消息Message——確切的說,是在handleMessage()方法中接收。

通過這種方式,客戶端不需要調用服務中的“方法”。取而代之的是,客戶端發送“消息”(Message對象),服務則接收位於Handler中的這個消息。

以下是服務使用一個Messenger做為接口的簡單例子:

publicclassMessengerServiceextendsService{
/** 發送給服務的用於顯示信息的指令*/
staticfinalintMSG_SAY_HELLO=1;

/**
* 從客戶端接收消息的Handler
*/
classIncomingHandlerextendsHandler{
@Override
publicvoidhandleMessage(Messagemsg){
switch(msg.what){
caseMSG_SAY_HELLO:
Toast.makeText(getApplicationContext(),"hello!",Toast.LENGTH_SHORT).show();
break;
default:
super.handleMessage(msg);
}
}
}

/**
* 向客戶端公布的用於向IncomingHandler發送信息的Messager
*/
finalMessengermMessenger=newMessenger(newIncomingHandler());

/**
* 當綁定到服務時,我們向Messager返回接口,
* 用於向服務發送消息
*/
@Override
publicIBinderonBind(Intentintent){
Toast.makeText(getApplicationContext(),"binding",Toast.LENGTH_SHORT).show();
returnmMessenger.getBinder();
}
}

請注意Handler中的handleMessage()方法,這裡是服務接收輸入消息Message的地方,也是根據what數字來決定要執行什麼操作的地方。

客戶端要做的全部工作就是根據服務返回的IBinder創建一個Messenger,並用send()方法發送一個消息。例如,以下是一個activity示例,它綁定到上述服務,並向服務發送MSG_SAY_HELLO消息:

publicclassActivityMessengerextendsActivity{
/** 用於和服務通信的Messenger*/
MessengermService=null;

/** 標識我們是否已綁定服務的標志 */
booleanmBound;

/**
* 與服務的主接口進行交互的類
*/
privateServiceConnectionmConnection=newServiceConnection(){
publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){
// 與服務建立聯接後將會調用本方法,
// 給出用於和服務交互的對象。
// 我們將用一個Messenger來與服務進行通信,
// 因此這裡我們獲取到一個原始IBinder對象的客戶端實例。
mService=newMessenger(service);
mBound=true;
}

publicvoidonServiceDisconnected(ComponentNameclassName){
// 當與服務的聯接被意外中斷時——也就是說服務的進程崩潰了,
// 將會調用本方法。
mService=null;
mBound=false;
}
};

publicvoidsayHello(Viewv){
if(!mBound)return;
// 創建並向服務發送一個消息,用到了已約定的'what'值
Messagemsg=Message.obtain(null,MessengerService.MSG_SAY_HELLO,0,0);
try{
mService.send(msg);
}catch(RemoteExceptione){
e.printStackTrace();
}
}

@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}

@Override
protectedvoidonStart(){
super.onStart();
// Bind to the service
bindService(newIntent(this,MessengerService.class),mConnection,
Context.BIND_AUTO_CREATE);
}

@Override
protectedvoidonStop(){
super.onStop();
// Unbind from the service
if(mBound){
unbindService(mConnection);
mBound=false;
}
}
}

請注意,上述例子中沒有給出服務是如何響應客戶端的。如果你需要服務進行響應,那你還需要在客戶端創建一個Messenger。然後,當客戶端接收到onServiceConnected()回調後,它再發送一個消息Message給服務,消息的send()方法中的replyTo參數裡包含了客戶端的Messenger。

在MessengerService.java(服務)和MessengerServiceActivities.java(客戶端)例程中,你可以看到如何雙向發送消息的例子。

綁定一個服務


應用程序組件(客戶端)可以通過調用bindService()來綁定服務。然後Android系統會調用服務的onBind()方法,返回一個用於和服務進行交互的IBinder。

綁定是異步進行的。bindService()將立即返回,並不會向客戶端返回IBinder。為了接收IBinder,客戶端必須創建一個ServiceConnection的實例,並把它傳給bindService()。ServiceConnection包含了一個回調方法,系統將會調用該方法來傳遞客戶端所需的那個IBinder。

注意:

只有activity、服務和content provider才可以綁定到服務上——你不能從廣播接收器(broadcast receiver)中綁定服務。

因此,要把客戶端綁定到服務上,你必須:

實現ServiceConnection。 你的實現代碼必須重寫兩個回調方法: onServiceConnected() 系統調用該方法來傳遞服務的onBind()方法所返回的IBinder。 onServiceDisconnected() 當與服務的聯接發生意外中斷時,比如服務崩潰或者被殺死時,Android系統將會調用該方法。客戶端解除綁定時,不會調用該方法。 調用bindService(),傳入已實現的ServiceConnection。 當系統調用你的onServiceConnected()回調方法時,你可以利用接口中定義的方法開始對服務的調用。 要斷開與服務的聯接,請調用unbindService()。 當客戶端被銷毀時,與服務的綁定也將解除。但與服務交互完畢後,或者你的activity進入pause狀態時,你都應該確保解除綁定,以便服務能夠在用完後及時關閉。(綁定和解除綁定的合適時機將在後續章節中繼續討論。)

例如,以下代碼段將客戶端與前面#擴展Binder類創建的服務聯接,而要做的全部工作就是把返回的IBinder轉換(cast)為LocalService類,並獲取LocalService的實例:

LocalServicemService;
privateServiceConnectionmConnection=newServiceConnection(){
// 與服務的聯接建立之後將會調用
publicvoidonServiceConnected(ComponentNameclassName,IBinderservice){
// 因為我們已經與明顯是運行於同一進程中的服務建立了聯接,
// 我們就可以把它的IBinder轉換為一個實體類並直接訪問它。
LocalBinderbinder=(LocalBinder)service;
mService=binder.getService();
mBound=true;
}

// 與服務的聯接意外中斷時將會調用
publicvoidonServiceDisconnected(ComponentNameclassName){
Log.e(TAG,"onServiceDisconnected");
mBound=false;
}
};

利用這個ServiceConnection,客戶端就能夠把它傳入bindService()完成與服務的綁定。例如:

Intentintent=newIntent(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異常,當聯接中斷時會拋出該異常。這是遠程方法唯一會拋出的異常。 對象的引用計數是跨進程的。 你通常應該成對地進行綁定和解除綁定,並與客戶端生命周期的啟動和結束過程相呼應。比如: 如果僅當你的activity可見時才需要與服務進行交互,則你應該在onStart()中進行綁定,並在onStop()中解除綁定。 如果你的activity需要在stopped後並進入後台期間仍然能接收響應,則你可以在onCreate()中進行綁定,並在[1]中解除綁定。請注意這表明你的activity在整個運行期間都需要使用服務(即使在後台),因此假如服務位於其它進程中,則你會增加進程的重量級,進程也會更容易被系統殺死。

注意:你通常應該在activity的onResume()和onPause()中綁定和解除綁定,因為這兩個回調方法在每次切換生命周期狀態時都會發生,這時你應該讓處理工作最少化。而且,如果應用程序中有多個activity都綁定到同一個服務上,則在兩個activity間切換時都會發生狀態轉換,因為當前activity解除綁定(在pause時)後,緊接著下一個activity又會進行綁定(resume時),所以服務也許在銷毀後馬上就要重建。(這種activity狀態轉換、多個activity間的生命周期協作在Activities文檔中描述。)

更多展示綁定服務的示例代碼,請參閱ApiDemos中的RemoteService.java類。

管理Bound服務的生命周期


 

一旦服務被所有客戶端解除綁定,則Android系統將會銷毀它(除非它同時又是用onStartCommand()started)。因此,如果你的服務就是一個純粹的bound服務,那你就不需要管理它的生命周期——Android系統會替你管理,根據是否還有客戶端對其綁定即可。

不過,如果你選擇實現onStartCommand()回調方法,那麼你就必須顯式地終止服務,因為此服務現在已經被視為started了。這種情況下,無論是否還存在客戶端與其綁定,此服務都會運行下去,直至自行用stopSelf()終止或由其它組件調用stopService()來終止。

此外,如果你的服務是started且允許被綁定,那麼系統調用你的onUnbind()方法時,你可以選擇返回true。這樣作的結果就是,下次客戶端綁定時將會收到onRebind()調用(而不是收到onBind()調用)。onRebind()返回void,但客戶端仍然能在它的onServiceConnected()回調方法中收到IBinder。圖1展示了這種生命周期的運行邏輯。

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved