[翻譯]Android Bound Services

一個bound service是一個client-server接口中的server端。一個bound service容許應用組件(好比activities)bind到它,發送請求,接收響應,甚至是執行進程間通訊(IPC)。一個bound service在典型狀況下,只有在它服務於另外一個應用組件時才存活,而不是在後臺無限期的運行。html

這份文檔向您說明了要如何建立bound service,包括在其餘的應用組件中如何bind到service。然而,你也應該參考Services文檔來大致地瞭解關於services的額外信息,好比如何在service中傳送通知,設置service在前臺運行,等等。java

基本概念

一個bound service是一個Service類的實現,它容許其它應用bind到它並與它交互。爲了給一個service提供binding功能,你必須實現onBind()回調方法。這個方法返回一個IBinder對象,該對象則定義了客戶端能夠用來與service進行交互的編程接口。android

Binding到一個Started Service

如同在Services文檔中討論的那樣,你能夠建立一個service,既能夠被started,也能夠被bound。即,service能夠經過調用startService()被started,從而容許service無限期的運行,也能夠容許一個客戶端經過調用bindService()來bind到service編程

若是你確實容許你的service被started和bound,則當service已經被started時,系統不會在全部的客戶端都unbind時銷燬那個service。相反,你必須經過調用stopSelf()stopService()來顯式地中止那個service。api

儘管你一般應該實現onBind()onStartCommand()之一,但有時須要同時實現二者。好比,一個音樂播放器可能會發現同時容許它的service無限期的運行及提供binding是頗有用的。以這種方式,一個activity能夠start service來播放一些音樂,而在用戶離開了應用後音樂也能夠繼續播放。而後,在用戶回到應用時,activity能夠bind到service來恢復對回放的控制。安全

請確保閱讀了關於管理一個Bound Service的生命週期的部分,來了解更多關於給一個started service添加binding功能時service生命週期的信息。多線程


一個客戶端能夠經過調用bindService()來bind到service。當它bind時,它必須提供一個ServiceConnection的實現,其者監視與service之間的鏈接。bindService()將當即返回,而且沒有返回值,但當Android系統建立客戶端和service之間的鏈接時,它會調用ServiceConnectiononServiceConnected(),來傳送IBinder給客戶端用於和service進行通訊。併發

然而,系統只有在第一個客戶端binds時纔會去調用你的service的onBind()來獲取IBinder。隨後系統傳送相同的IBinder給其它bind的客戶端,而再也不次調用onBind()app

當最後一個客戶端從service unbinds時,系統將銷燬service(除非service也經過startService()被started了)。dom

當你實現你的bound service時,最重要的部分是定義你的onBind()回調方法返回的接口。你能夠用一些不一樣的方法來定義你的service的IBinder接口,下面的小節將討論每種技術。

建立一個Bound Service

當建立一個提供binding功能的service時,你必須提供一個客戶端能夠用於與service端交互的提供了編程接口的IBinder。有三種方法你能夠用來定義這樣的接口:

  • 擴展Binder類

    若是你的service是你本身的應用私有的,而且與客戶端運行在相同的進程中(很常見的狀況),你應該經過擴展Binder類來建立你的接口,並在onBind()方法中返回一個它的實例。客戶端接收到Binder,而後就可使用它來直接訪問Binder實現或Service的可用的public方法。

    當你的service只是你本身的應用的一個後臺工做者時,這是首選的技術。不使用這種方式來建立你的接口的僅有的理由就是,你的service被其餘應用或跨越不一樣的進程被用到了。

  • 使用一個Messenger

    若是你須要你的接口跨進程工做,你能夠藉助於一個Messenger來爲你的service建立一個接口。用這種方式,service定義了一個Handler,來響應不一樣類型的Message對象。這個Handler是一個Messenger的基礎,而後後者能夠與客戶端共享一個IBinder,容許客戶端使用Message對象給service發送命令。此外,客戶端能夠定義一個它本身的Messenger以使service能夠發送消息回去。

    這是執行進程間通訊(IPC)最簡單的方式,由於Messenger把全部的請求入隊到一個單獨的線程,所以你不須要爲線程安全而外設計你的service。

  • 使用AIDL

    AIDL (Android Interface Definition Language)執行全部將對象分解成操做系統能夠理解的元語的工做,並在進程之間處理它們來執行IPC。前面的一種技術,即便用一個Messenger,其實是以AIDL爲它的底層結構的。如上面提到的,Messenger在一個單獨的線程中建立一個全部的客戶端請求的隊列,以使service在某一時刻只接收一個請求。若是,然而,你想要你的service並行的處理多個請求,那麼你能夠直接使用AIDL。在這種狀況下,你的service必須具備多線程的能力,而且要被構建的是線程安全的。

    要直接使用AIDL,你必須建立一個定義了編程接口的.aidl文件。Android SDK工具使用這個文件來產生一個實現了接口並處理IPC的抽象類,隨後你能夠在你的service中擴展這個抽象類。

注意:大多數應用不該該使用AIDL來建立一個bound service,由於它可能須要多線程能力,並可能致使一個更復雜的實現。所以,AIDL對於大多數應用都是不合適的,而且這份文檔中不討論如何使用它來實現你的service。若是你確認你須要直接使用AIDL,請參考AIDL文檔。

擴展Binder類

若是你的service只被本地應用用到了,而且不須要跨進程工做,那麼你能夠實現你本身的Binder類,來爲你的客戶端提供直接訪問service中的public方法的能力。

注意:這種方法只有在客戶端和service在相同的應用和進程中時纔有效,固然這種狀況很常見。例如,對於一個音樂應用,須要bind一個activity到它本身的在後臺播放音樂的service,就頗有效。

這裏是如何設置它:

  1. 在你的service中建立一個下述類型之一的Binder實例:

    (1). 包含了客戶端能夠調用的public方法

    (2). 返回當前的Service實例,其中具備客戶端能夠調用的public方法

    (3). 或者,返回另外一個類的實例,其中寄宿了service,具備客戶端能夠調用的public方法。

  2. onBind()回調方法中返回這個Binder實例。

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

注意:service和客戶端必須位於相同的應用中的理由是,這樣客戶端能夠強制類型轉換返回的對象,並適當地調用它的APIs。service和客戶端必須處於相同的進程,因爲這項技術不執行任何的跨進程處理。

好比,這兒有一個service,它經過一個Binder實現,提供客戶端訪問service中的方法:

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實例。這將容許客戶端調用service中的public方法。好比,客戶端能夠調用service的getRandomNumber()。

這裏是一個activity,它bind到LocalService,並在button被點擊時調用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()回調bind到service。下一節將提供更多關於binding到service的這一過程的信息。

注意:上面的例子沒有顯式地從service unbind,但全部的客戶端都應該在一個適當的時間點 (好比當activity pause時) unbind。

.更多示例代碼,請參考ApiDemos中的LocalService.java類和LocalServiceActivities.java類。

使用一個Messenger

與AIDL的比較

當你須要執行IPC時,使用一個Messenger來實現你的接口比經過AIDL來實現它要簡單,由於Messenger將全部的調用入隊到service,然而,一個純粹的AIDL接口發送併發的請求給service,這將必需要處理多線程。

對於大多數應用,service不須要執行多線程,所以使用一個Messenger容許service每次只處理一個調用。若是你的service必定要是多線程的,那麼你應該使用AIDL來定義你的接口。


若是你須要你的service與遠端進程通訊,那麼你可使用一個Messenger來爲你的service提供接口。這項技術容許你在不須要使用AIDL的狀況下執行進程間通訊(IPC)。

這裏是一個如何使用一個Messenger的總結:

以這種方式,客戶端不調用service的"方法"。而是,客戶端傳送"消息" (Message 對象),service在它的Handler中接收消息。

這裏有一個簡單的使用了Messenger接口的示例service:

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()方法正是service接收傳入的Message並根據what成員決定作什麼的地方。

一個客戶端所須要作的就是建立一個基於service返回的IBinderMessenger,並使用send()來發送一個消息。好比,這裏是一個簡單的activity,它binds到service,並傳送MSG_SAY_HELLO消息給service:

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;
        }
    }
}

這個例子沒有演示service能夠如何響應客戶端。若是你想要service響應,則你也須要在客戶端建立一個Messenger。而後當客戶端接收到onServiceConnected()回調時,它發送一個Message 給service,其中在send()方法的replyTo參數中包含了客戶端的Messenger

你能夠參考示例MessengerService.java (service)和MessengerServiceActivities.java (client)來看一下如何提供兩路消息的例子。

Binding到一個Service

應用組件(客戶端)能夠經過調用bindService()來bind到一個service。而後,Android系統調用service的onBind()方法,它會返回一個IBinder用於與service交互。

binding是異步的。bindService()會當即返回,而不給客戶端返回IBinder。要接收IBinder,客戶端必須建立一個ServiceConnection的實例,並把它傳遞給bindService()ServiceConnection包含一個回調方法,系統能夠調用它來傳送IBinder

注意:只有activities,services,和content providers能夠bind到一個service——你不能在一個broadcast receiver中bind到一個service。

於是,要在客戶端中bind到一個series,你必須:

  1. 實現ServiceConnection.

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

    onServiceConnected()

        系統調用這個方法來傳送service的onBind()方法返回的IBinder

    onServiceDisconnected()

        當與service的鏈接意外斷開時,Android系統會調用它。好比當service發生了crash或已       經被殺掉時。當client unbinds時則不會被調到。

  2. 調用bindService(),並傳入ServiceConnection的實現。

  3. 當系統調用了你的onServiceConnected()回調時,你就能夠開始調用service了,使用接口所定義的方法。

  4. 要從service斷開鏈接,則調用unbindService()

    當你的客戶端被銷燬時,它將從service unbind,但你應該老是在你完成了與service的交互或者你的activity pause時unbind,以使得service能夠在它沒有被用到時關閉。(下面有更多關於bind和unbind的合適的時間的討論。)

例如,下面的代碼片斷把客戶端與上面經過擴展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 disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "onServiceDisconnected");
        mBound = false;
    }
};

藉助於這個ServiceConnection,客戶端能夠經過把它傳遞給bindService()來bind到一個service。好比:

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

補充說明

這裏是關於binding到一個service的一些重要的說明:

  • 你應該老是捕獲DeadObjectException一場,當鏈接破壞時它會被拋出。這是遠程方法拋出的僅有的異常。

  • 對象是在進程間引用計數的。

  • 你一般應該在客戶端的生命週期中對應的bring-up和tear-down的時刻成對的調用binding和unbinding。好比:

    若是你只須要在你的activity可見時與service交互,則你應該在onStart()中bind,並在onStop()unbind。

    若是你想要你的activity即便已經被stopped了,也要在後臺接收響應,則你能夠在onCreate()中bind,並在onDestroy()中unbind。注意,這意味着你的activity須要在它運行的全部時間都使用service(即便是在後臺),所以若是service在另外一個進程,則你將增長那個進程的weight,而且它變得更可能被系統殺掉。

注意:一般你不該該在你的activity的onResume()onPause()中bind和unbind,由於這些回調在每個生命週期事務中都會發生,你應該把這些事務中發生的處理量降到最低。若是你的應用中的多個activities bind到相同的service,而且在這些activities中的兩個之間有一個事務,則在當前的activity unbinds (在pause中)以後,下一個binds (在resume中) 以前可能會發生service的銷燬和重建。(這種activities如何協調他們的生命週期的activity事務,在Activities文檔中有描述)。

更多演示如何bind到一個service的示例代碼,能夠參考ApiDemos中的RemoteService.java類。

管理一個Bound Service的生命週期

當全部的客戶端都從service unbound時,Android系統會銷燬它(除非它也同時經過onStartCommand()被started了)。所以,你不須要管理你的service的生命週期,若是它是純粹的bound service的話——Android系統會基於它是否被bound到了客戶端來爲你管理它。

然而,若是你選擇實現onStartCommand()回調方法,則你須要顯式的來中止service,由於如今service被認爲是被started的了。在這種狀況下,service會一直運行,直到service經過stopSelf()來終止它本身,或者另外一個組件調用stopService(),而無論它是否被bound到任何的客戶端。

此外,若是你的service是被started的,並接受binding,那麼當系統調用了你的onUnbind()方法,若是你想要在下次客戶端binds到service時接收一個對onRebind()的調用(而不是接收一個對onBind()的調用),你能夠選擇返回true。onRebind()返回void,可是客戶端仍然在它的onServiceConnected()回調中接收IBinder。下面的圖1描繪了這種生命週期的邏輯。

圖1. The lifecycle for a service that is started and also allows binding.

更多關於一個started的service的生命週期的信息,請參考Services文檔。

Done。

相關文章
相關標籤/搜索