天天看點

Android Api Component---翻譯Bound Service

bound service是一個client-server接口中的server端。bound service允許元件(像activity)綁定service,發送請求,接受響應,并且甚至執行程序間通信(IPC)。典型的bound service寄宿于它服務的另一個應用程式元件中,并且不會無限期的運作于背景。

這篇文檔教你如何建立一個bound service,也包含了如何從其他的應用程式元件中綁定service。然而,一般來說,你也應該參考Service文檔取得關于service的額外的一些資訊,像如何從service中傳遞通知,設定service運作于前台等等。

基礎

一個bound service是一個Service類的實作,并且允許其它應用程式綁定它并與之互動。為了給一個service提供綁定,你必須實作onBind()回調方法。這個方法傳回一個定義了一個可以讓用戶端與service互動的IBinder接口對象。

用戶端可以通過bindService()綁定到這個service。當用戶端這樣做的時候,它必須提供一個監視與service的連接配接的ServiceConnection的實作。這個bindService()方法在沒有值時立即傳回,但是當Android系統在用戶端和service之間建立連接配接的時候,它會在ServiceConnection上調用onServiceConnected()傳遞用戶端與service通信的IBinder。

多個用戶端可以立刻連接配接到這個service上。然而,隻有當你的第一個用戶端綁定的時候系統才會調用你的service的onBind()方法獲得IBinder。然後系統在沒有再一次調用onBind()的時候會給額外的綁定service的用戶端傳遞相同的IBinder。

當最後一個用戶端從service中解開的時候,系統就銷毀service(除非這個service又通過startService()被開啟)。

當你實作你的bound service的時候,最重要的部分就是定義你的onBind()回調方法傳回的接口。你可以用幾種不同的方式來定義你的service的IBinder接口并且下面的段落會讨論每一個技術。

建立一個Bound Service

當建立一個提供綁定的service的時候,你必須提供一個可以讓用戶端與service互動的程式接口IBinder。你可以用三種方式定義這個接口:

繼承Binder類

    如果你的service對你的應用程式是私有的并且與用戶端(它是共有的)一樣運作在相同的程序中,那麼你就應該通過繼承Binder類建立你的接口并且從onBind()傳回一個它的執行個體。用戶端接收Binder并且可以使用它在要麼是Binder實作中要麼是甚至Service中直接通路有效的公共方法。

    當你的service僅僅隻為你的應用程式作為背景工作者的時候,這是被推薦的技術。你不能用這種方式建立你的接口的唯一原因是因為你的service被其它的應用程式使用或者通路分隔的程序。

使用信号    

    如果你需要你的接口在工作的時候通路不同的程序,你可以用Messenger給你的service建立一個接口。這種方式是service定義一個句柄來響應不同的Messenge對象的類型。這個可以跟用戶端共享一個IBinder以及允許用戶端使用Message對象給service發送指令的Handler是Messenger的基礎,此外,用戶端可以定義一個自己的Messenger以至于service可以來回的發送消息。

    這是最簡單的程序間通信,因為Messenger讓所有的請求排隊到一個單個的線程中以至于你不需要把你的service設計為線程安全的。

使用AIDL

    AIDL(Android Interface Definition Language)将所有的工作對象分解為作業系統了解的單元并且組織它們通路程序來執行IPC。前面使用Messenger的技術其實是AIDL的底層架構基礎。正如上面被提及的,這個Messenger在單個線程中建立了所有的用戶端的請求隊列,是以service一次接收一個請求。然而如果你想讓你的service同僚處理多個請求,那麼你就可以直接使用AIDL。在這個例子中,你的service一定是多線程的并且被建構成了線程安全的。

    為了直接使用AIDL,你必須建立一個.aidl檔案來定義程式接口。Android SDKtools使用這個檔案生成一個實作接口并且處理IPC的抽象類,你可以在你的service中擴充這個類。

注意:大多數應用程式不應該使用AIDL建立bound service,因為它也許要求多線程性能并且可能導緻更多的複雜實作。同樣的,AIDL不适用于這篇文檔中讨論的教你給你的service如何使用它的。如果你确信你需要直接使用AIDL,那看AIDL文檔。

擴充Binder類

如果你的應用程式隻在你的本地應用程式中使用并且不需要通路程序工作,那麼你可以實作你自己的Binder類,在你的service中直接給你的用戶端提供通路公共方法。

注意:這種工作緊緊是如果用戶端和service是在相同的應用程式和程序中,它們都是相同的。例如,這種方式很可能會讓一個給運作在背景的它自己的service綁定了activity的音樂應用程式運作良好。

這是如何設定的步驟:

    1.你的service中建立一個Binder的執行個體,可選的:

        包含用戶端可以調用的公共方法

        傳回目前的有用戶端可以調用的公共方法的Service執行個體

        或者通過service傳回主體中另一個帶有可以讓用戶端調用的公共方法的類

    2.從onBinder()回調方法中傳回Binder的執行個體

    3.在這個用戶端中,從onServiceConnected()回調方法中接收Binder并且使用被提供的方法回調bound service。

注意:service和用戶端必須在相同應用程式中的原因是用戶端可以轉換傳回的對象并且适當的調用它的API。service和用戶端也必須在相同的程序中是因為這種技術部執行任何順序的通路程序。

例如,這是一個提供用戶端在service中通過一個Binder實作通路方法的service:

public class LocalService extends Service {
    //Bind 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 publisc methods
            return LocalService.this;
        }
    }
    
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
    
    /**method for clients*/
    public int mgetRandomNumber() {
        return mGenerator.nextInt(100);
    }
}
           

這個LocalBinder給用戶端提供了getService()方法傳回目前的LocalService的執行個體。這就允許用戶端在service中調用公共方法。例如,用戶端可以從service中調用getRandomNumber()。

這是一個當一個按鈕按下的時候綁定了LocalService并且調用getRandomNumber()的activity:

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();
        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()回調的實作綁定到service。下面的段落提供了更多關于處理綁定到service的資訊。

注意:上面的例子不是從service中顯示的解開,而是所有的用戶端在适當的時間解開(比如activity被停止的時候)。

使用Messenger

如果你需要你的service跟遠端程序通信,那麼你可以使用Messenger給你的service提供接口。這種技術允許你在不需要使用AIDL的情況下執行程序間通信。

這是一個如何使用Messenger的概述:

    這個service實作了一個從一個用戶端給每一個調用接收一個回調的Handler。

    這個Handler用于建立一個Messenger對象(它是對Handler的一個映射)。

    這個Messenger建立了service從onBind()傳回給用戶端的一個IBinder。

    用戶端使用IBinder初始化一個用于給這個service發送Message對象的Messenger。

    這個service在它的在handleMessage()方法中指定的Handler中擷取每一個Message。

用這種方式,就給用戶端沒有方法來調用service。相反,用戶端傳遞service在它的Handler中接收的"messages"(Message對象)。

這是一個service使用一個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 binging 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 mMessage.getBinder();
    }
}
           

注意在Handler中的handleMessage()方法是service基于what member接收輸入的Message的并且決定做什麼的地方。

所有的用戶端需要做的是通過service基于IBinder建立一個Messenger并且使用send()發送一個message。例如,這是一個簡單的綁定了service的并且給service傳遞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
            //unexpectedlly 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;
        }
    }
}
           

注意這個例子沒有展示service如果給用戶端響應。如果你想讓service響應,那麼你也需要在用戶端建立一個Messenger。然後當用戶端接收onServiceConnected()回調的時候,給service在send()方法的replyTo參數中發送一個包含用戶端的Messenger的Message。

你可以在MessengerService.java和MessengerServiceActivities.java中看一個如何提供兩種方式的messaging的例子。

綁定到service

應用程式元件能夠通過調用bindService()綁定一個service。Android系統然後調用service的傳回一個跟service互動的IBinder的onBind()方法。

這個綁定是異步的。bindService()立即傳回并且不會給用戶端傳回IBinder。為了擷取IBinder,用戶端必須建立一個ServiceConnection的執行個體并且傳遞給bindService()。ServiceConnection包含一個系統調用傳遞IBinder的回調方法。

注意:隻有activities,services和content providers可以綁定一個service,你不能從一個broadcast receiver中綁定一個service。

是以,為了從你的用戶端綁定一個service,你必須:

  1. 實作ServiceConnection.

    你的實作必須覆寫兩個回調方法:

    onServiceConnected()

      系統調用這個方法來傳遞通過service的onBind()方法傳回的IBinder。

    onServiceDisconnected()

      當連接配接到這個service出現未預期的掉線時,Android系統會調用它,比如當service奔潰或者被殺死。當用戶端被解開的時候不會被調用。

  2. 調用bindService()傳遞ServiceConnection實作。
  3. 當系統調用你的onServiceConnected()回調方法的時候,你能夠使用通過這個接口定義的方法開始調用service。
  4. 從service中斷開連接配接,調用unbindService()。

    當你的用戶端被銷毀的時候,它将從service中解開,但是當你完成了與service的互動或者當你的activity暫停以至于這個service當它不被使用的時候被關閉的時候你應該總是解開。

例如,下面的片段連接配接用戶端到通過繼承Binder類在上面建立的service,是以它唯一必須做的是将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 disconnectes unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceConnected");
        mBound = false;
    }
};
           

通過ServiceConnection,用戶端通過給bindService()傳遞它可以綁定到一個service。例如:

Intent intent = new Intent(this, LocalService.class);
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
           

bindService()的第一個參數是一個綁定給這個service的顯式名字的Intent。

第二個參數是ServiceConnection對象。

第三個參數是一個标明綁定的标記。它通常應該是BIND_AUTO_CREATE為的是如果它的已經不是存貨的就建立這個service。其它的可能的值時BIND_DEBUG_UNBIND和BIND_NOT_FOREGROUND或者是0.

額外的注解

這是一些關于綁定到service的重要的注意點:

  1. 你應該總是捕獲DeadObjectException異常,當連接配接斷開之後它會被抛出。這僅僅是通過遠端方法抛出的異常。
  2. 對象是引用技術通路的程序。
  3. 通常你應該在開啟和拆除用戶端生命周期的時刻的時候比對結對的綁定與解開。例如:

    當你的activity是可見的時候如果你隻需要跟service互動,你應該在onStart()期間綁定并且在onStop()期間解開。

    如果你想讓你的activity接收響應甚至是在背景被停止的時候,那麼你可以在onCreate()期間綁定并且在onDestroy()期間解開。注意這就意味着你的activity需要使用service運作期的整個時間(甚至在背景),是以如果這個service在另一個程序中,那麼你增加了讓系統更可能會殺死的程序的權重。

注意:你通常不應該在你的activity的onResume()和onPause()期間綁定和解開,因為這個回調會出現在每個生命周期事件并且你應該讓程序出現在事件裡的事件最小化。還有,如果在你的應用程式中的對個activity綁定了相同的service并且在那些activity之間有一個事物,這個service在下一個綁定解開以前也許會銷毀并且重新被建立作為目前activity。

對于更多的例子代碼,看在ApiDemos中的RemoteService.java類如何綁定service。

管理綁定的service的生命周期

當一個service從所有的用戶端解開的時候,Android系統銷毀它(除非它又用onStartCommand()開啟)。當然,如果你的service是一個純粹的由Android系統基于是否綁定到任何用戶端替你管理bound servcie,你不需要管理你的service的生命周期。

然而,如果你選擇實作onStartCommand()回調方法,那麼你必須顯式的停止service,因為這個service現在被認為是開啟的。在這個例子中,除非這個service用stopSelf()停止它自己或者另一個元件調用stopService()停止這個service,否則這個service會一直運作。

此外,如果你的service被開啟并且接受了綁定,那麼當系統調用你的onUnbind()方法的時候,你可以有選擇的傳回true如果你想獲得一個下一次對一個用戶端onRebind()的調用。onRebind()傳回void,但是用戶端任然在它的onServiceConnected()回調中接收IBinder。下面的圖是這個邏輯的生命周期類型:

Android Api Component---翻譯Bound Service