四大組件之Service_綁定服務

[文章內容來自Developers]javascript

綁定服務是客戶端-服務器接口中的服務器。綁定服務可以讓組件(例如 Activity)綁定到服務、發送請求、接收響應,甚至執行進程間通訊 (IPC)。 綁定服務一般只在爲其餘應用組件服務時處於活動狀態,不會無限期在後臺運行。html

基礎知識


綁定服務是 Service類的實現,可以讓其餘應用與其綁定和交互。要提供服務綁定,您必須實現 onBind()回調方法。該方法返回的 IBinder對象定義了客戶端用來與服務進行交互的編程接口。java

客戶端可經過調用 bindService()綁定到服務。調用時,它必須提供 ServiceConnection的實現,後者會監控與服務的鏈接。bindService()方法會當即無值返回,但當 Android 系統建立客戶端與服務之間的鏈接時,會對 ServiceConnection調用 onServiceConnected(),向客戶端傳遞用來與服務通訊的 IBinder。
多個客戶端可同時鏈接到一個服務。不過,只有在第一個客戶端綁定時,系統纔會調用服務的onBind()方法來檢索 IBinder。系統隨後無需再次調用 onBind(),即可將同一 IBinder傳遞至任何其餘綁定的客戶端。
當最後一個客戶端取消與服務的綁定時,系統會將服務銷燬(除非 startService()也啓動了該服務)。
當您實現綁定服務時,最重要的環節是定義您的 onBind() 回調方法返回的接口。您能夠經過幾種不一樣的方法定義服務的 IBinder 接口,下文對這些方法逐一作了闡述。android

建立綁定服務


建立提供綁定的服務時,您必須提供 IBinder,用以提供客戶端用來與服務進行交互的編程接口。 您能夠經過三種方法定義接口:編程

擴展 Binder 類
若是服務是供您的自有應用專用,而且在與客戶端相同的進程中運行(常見狀況),則應經過擴展 Binder類並從 onBind()返回它的一個實例來建立接口。客戶端收到 Binder 後,可利用它直接訪問 Binder 實現中乃至 Service中可用的公共方法。若是服務只是您的自有應用的後臺工做線程,則優先採用這種方法。 不以這種方式建立接口的惟一緣由是,您的服務被其餘應用或不一樣的進程佔用。api

使用 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 可綁定到服務,從新得到回放控制權。
請務必閱讀管理綁定服務的生命週期部分,詳細瞭解有關爲已啓動服務添加綁定時該服務的生命週期信息。app

擴展 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 說明了這種生命週期的邏輯。

圖 1. 容許綁定的已啓動服務的生命週期。
相關文章
相關標籤/搜索