Android中的Service:Binder,Messenger,AIDL(2)

前言

前面一篇博文介紹了關於Service的一些基本知識,包括service是什麼,怎麼建立一個service,建立了一個service以後如何啓動它等等。在這一篇博文裏有一些須要前一篇鋪墊的東西,建議沒有看過前一篇博文的同窗先去看一下前一篇: Android中的Service:默默的奉獻者 (1)java

可是在前一篇博文中也有一些遺漏的東西——主要是關於bindService()這一塊的具體細節。因爲這一塊涉及的東西仍是比較多,因此在這裏單獨提出來了。閒話很少說,進入正文。android

正文

1,bindService()

先溫故一下上一篇博文的一些內容:web

這是一種比startService更復雜的啓動方式,同時使用這種方式啓動的service也能完成更多的事情,好比其餘組件可向其發送請求,接受來自它的響應,甚至經過它來進行IPC等等。咱們一般將綁定它的組件稱爲客戶端,而稱它爲服務端。
    若是要建立一個支持綁定的service,咱們必需要重寫它的onBind()方法。這個方法會返回一個IBinder對象,它是客戶端用來和服務器進行交互的接口。而要獲得IBinder接口,咱們一般有三種方式:繼承Binder類,使用Messenger類,使用AIDL。

要完成客戶端與服務端的綁定,有兩件事要作。一是在客戶端完成bindService的調用以及相關配置,二是在服務端裏面實現onBind()方法的重寫,返回一個用作信息交互的IBinder接口。接下來咱們就一塊一塊的來看它的實現方法。數據庫

1.1,客戶端的配置

客戶端原則上來說調用bindService()方法就能夠了,然而事實並無這麼簡單。緣由就出在bindService()這個方法身上。下面咱們來詳細的瞭解一下這個方法:服務器

public boolean bindService(Intent service, ServiceConnection conn, int flags) {
    return mBase.bindService(service, conn, flags);
}

能夠看到,bindService()方法須要三個參數,第一個是一個intent,咱們都很熟悉——它和startService()裏面那個intent是同樣的,用來指定啓動哪個service以及傳遞一些數據過去。第二個參數可能就有點陌生了,這是個啥?這是實現客戶端與服務端通訊的一個關鍵類。要想實現它,就必須重寫兩個回調方法:onServiceConnected()以及onServiceDisconnected(),而咱們能夠經過這兩個回調方法獲得服務端裏面的IBinder對象,從而達到通訊的目的(下文對此會有更加詳細的介紹)。下面是一個例子:多線程

ServiceDemo mService;
//BinderDemo是在ServiceDemo裏面的一個繼承了Binder的內部類,這是一種獲得IBinder接口的方式
//下文會有詳述
ServiceDemo.BinderDemo mBinder;

private ServiceConnection mConnection = new ServiceConnection() {

    //系統會調用該方法以傳遞服務的 onBind() 方法返回的 IBinder。
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        //當系統調用 onServiceConnected() 回調方法時,咱們可使用接口定義的方法開始調用服務。
        mBinder = (ServiceDemo.BinderDemo) service;
        //getService()是BinderDemo中的一個方法
        mService = mBinder.getService();
        //在此處能夠利用獲得的ServiceDemo對象調用該類中的構造方法
        Log.d(this.getClass().getSimpleName(), "onServiceConnected");
    }

    //Android系統會在與服務的鏈接意外中斷時(例如當服務崩潰或被終止時)調用該方法。當客戶端取消綁定時,系統「絕對不會」調用該方法。
    @Override
    public void onServiceDisconnected(ComponentName name) {
        Log.d(this.getClass().getSimpleName(), "onServiceDisconnected");
    }
};

上面的例子實現了一個比較普通的ServiceConnection的主要功能,咱們能夠經過它獲得目標service的對象,而後能夠調用其內的共有方法,實現客戶端與服務端交互的目的。併發

bindService()方法的第三個參數是一個int值,還叫flag(這flag立的),它是用來作什麼的呢?它是一個指示綁定選項的標誌,一般應該是 BIND_AUTO_CREATE,以便建立還沒有激活的服務。 其餘可能的值爲 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)。dom

ok,客戶端的配置到這裏就差很少搞定了,接下來看看服務端須要作些什麼。ide

1.2,服務端的配置

若是要建立一個支持綁定的service,咱們必需要重寫它的onBind()方法。這個方法會返回一個IBinder對象,它是客戶端用來和服務器進行交互的接口。而要獲得IBinder接口,咱們一般有三種方式:繼承Binder類,使用Messenger類,使用AIDL。

能夠看到,這裏提出了一個IBinder接口的概念。那麼這個IBinder接口是什麼呢?它是一個在整個Android系統中都很是重要的東西,是爲高性能而設計的輕量級遠程調用機制的核心部分。固然,它不只僅能夠用於遠程調用,也能夠用於進程內調用——事實上,咱們如今所說的service這裏的IBinder既有可能出現遠程調用的場景,好比用它來進行IPC,也有可能出現進程內調用的場景,好比用它來進行同進程內客戶端與服務器的交互。IBinder的具體的工做原理在這裏就不詳述了,之後我應該會就這一塊的內容單獨寫一系列的博客,在這裏只須要知道它是客戶端用來和服務器進行交互的接口,而且知道能夠怎樣經過IBinder來實現它們的交互就能夠了。svg

通常來說,咱們有三種方式能夠得到IBinder的對象:繼承Binder類,使用Messenger類,使用AIDL。接下來我將就這三種方式展開來說。

1.2.1,繼承Binder類

看到這裏可能有些同窗會問:Binder又是什麼?在這裏我並不許備給出一個詳盡、確切的答案——由於他太複雜了,深刻的講下去的話必將致使這篇博文失去重心。在這裏咱們只須要知道,它實現了IBinder接口,經過實現Binder類,咱們的客戶端能夠直接經過這個類調用服務端的公有方法。另外,雖然從IPC的角度來說,Binder是Android中的一種跨進程通訊方式,可是其實通常service裏面的Binder是不會涉及進程間通訊的,因此其在這種狀況下顯得較爲簡單。

下面咱們來看下經過繼承Binder類實現客戶端與服務端通訊應該怎樣作:

  • 在service類中,建立一個知足如下任一要求的Binder實例:
    • 包含客戶端可調用的公共方法
    • 返回當前Service實例,其中包含客戶端可調用的公共方法
    • 返回由當前service承載的其餘類的實例,其中包含客戶端可調用的公共方法
  • 在onBind()方法中返回這個Binder實例
  • 在客戶端中經過onServiceDisconnected()方法接收傳過去的Binder實例,並經過它提供的方法進行後續操做

能夠看到,在使用這種方法進行客戶端與服務端之間的交互是須要有一個強制類型轉換的——在onServiceDisconnected()中得到一個通過轉換的IBinder對象,咱們必須將其轉換爲service類中的Binder實例的類型才能正確的調用其方法。而這強制類型轉換其實就隱含了一個使用這種方法的條件:客戶端和服務端應當在同一個進程中!否則在類型轉換的時候也許會出現問題——在另外一個進程中必定有這個Binder實例麼?沒有的話就不能完成強制類型轉換。

下面是一個Google官方的例子:

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():

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

當Activity進入onStart()狀態時,就會嘗試與目標service綁定,而當點擊按鈕時,若是綁定已經完成,就會調用service中的方法getRandomNumber(),並將其輸出。

線程內通訊基本上就是這樣了,沒什麼複雜的地方。接下來咱們看看這種方式啓動的service如何進行IPC。

1.2.2,使用Messenger

之前講到跨進程通訊,咱們老是第一時間想到AIDL(Android接口定義語言),實際上,使用Messenger在不少狀況下是比使用AIDL簡單得多的,具體是爲何下文會有比較。

你們看到Messenger可能會很輕易的聯想到Message,而後很天然的進一步聯想到Handler——沒錯,Messenger的核心其實就是Message以及Handler來進行線程間的通訊。下面講一下經過這種方式實現IPC的步驟:

  • 服務端實現一個Handler,由其接受來自客戶端的每一個調用的回調
  • 使用實現的Handler建立Messenger對象
  • 經過Messenger獲得一個IBinder對象,並將其經過onBind()返回給客戶端
  • 客戶端使用 IBinder 將 Messenger(引用服務的 Handler)實例化,而後使用後者將 Message 對象發送給服務
  • 服務端在其 Handler 中(具體地講,是在 handleMessage() 方法中)接收每一個 Message

用這種方式,客戶端並無像擴展Binder類那樣直接調用服務端的方法,而是採用了用Message來傳遞信息的方式達到交互的目的。接下來是一個簡單的例子:

//服務端
public class MessengerServiceDemo extends Service {

    static final int MSG_SAY_HELLO = 1;

    class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_SAY_HELLO:
                    //當收到客戶端的message時,顯示hello
                    Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show();
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    }

    final Messenger mMessenger = new Messenger(new ServiceHandler());

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show();
        //返回給客戶端一個IBinder實例
        return mMessenger.getBinder();
    }
}

服務端主要是返給客戶端一個IBinder實例,以供服務端構造Messenger,而且處理客戶端發送過來的Message。固然,不要忘了要在Manifests文件裏面註冊:

<service  android:name=".ActivityMessenger" android:enabled="true" android:exported="true">
    <intent-filter>
        <action android:name="com.lypeer.messenger"></action>
        <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</service>

能夠看到,這裏註冊的就和咱們原先註冊的有一些區別了,主要是由於咱們在這裏要跨進程通訊,因此在另一個進程裏面並無咱們的service的實例,此時必需要給其餘的進程一個標誌,這樣才能讓其餘的進程找到咱們的service。講道理,其實這裏的android:exported屬性不設置也能夠的,由於在有intent-filter的狀況下這個屬性默認就是true,對這個有些遺忘的同窗能夠再去看一下上一篇博文: Android中的Service:默默的奉獻者 (1)

接下來咱們看下客戶端應當怎樣操做:

//客戶端
public class ActivityMessenger extends Activity {

    static final int MSG_SAY_HELLO = 1;

    Messenger mService = null;
    boolean mBound;

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            //接收onBind()傳回來的IBinder,並用它構造Messenger
            mService = new Messenger(service);
            mBound = true;
        }

        public void onServiceDisconnected(ComponentName className) {
            mService = null;
            mBound = false;
        }
    };

    //調用此方法時會發送信息給服務端
    public void sayHello(View v) {
        if (!mBound) return;
        //發送一條信息給服務端
        Message msg = Message.obtain(null, 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.activity_main);
    }

    @Override
    protected void onStart() {
        super.onStart();
        //綁定服務端的服務,此處的action是service在Manifests文件裏面聲明的
        Intent intent = new Intent();
        intent.setAction("com.lypeer.messenger");
        //不要忘記了包名,不寫會報錯
        intent.setPackage("com.lypeer.ipcserver");
        bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
    }

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

客戶端就主要是發起與服務端的綁定,以及經過onServiceConnected()方法來過去服務端返回來的IBinder,藉此構造Messenger,從而能夠經過發送Message的方式與服務端進行交互。上面的例子其實並不完整,由於它只有客戶端對服務端單方面的通訊,而服務端沒有發信息給客戶端的功能——這顯然是不合理的。而要實現這個其實也很簡單,只要客戶端裏也建立一個Handler實例,讓它接收來自服務端的信息,同時讓服務端在客戶端給它發的請求完成了以後再給客戶端發送一條信息便可。

用Messenger來進行IPC的話總體的流程是很是清晰的,Message在其中起到了一個信使的做用,經過它客戶端與服務端的信息得以互通。

1.2.3,經過AIDL

AIDL,即Android Interface Definition Language,Android接口定義語言。它是一種IDL語言,能夠拿來生成用於IPC的代碼。在我看來,它其實就是一個模板。爲何這樣說呢?在咱們的使用中,實際上起做用的並非咱們寫的AIDL代碼,而是系統根據它生成的一個IInterface實例的代碼。而若是你們多生成幾個這樣的實例,而後把它們拿來比較,你會發現它們都是有套路的——都是同樣的流程,同樣的結構,只是根據具體的AIDL文件的不一樣有細微的變更。因此其實AIDL就是爲了不咱們一遍遍的寫一些千篇一概的代碼而出現的一個模板。

那麼如何使用AIDL來經過bindService()進行線程間通訊呢?基本上有下面這些步驟:

  • 服務端建立一個AIDL文件,將暴露給客戶端的接口在裏面聲明
  • 在service中實現這些接口
  • 客戶端綁定服務端,並將onServiceConnected()獲得的IBinder轉爲AIDL生成的IInterface實例
  • 經過獲得的實例調用其暴露的方法

上面的描述其實比較抽象,基本上是那種看了也不知道怎麼作的類型——這個若是要展開講的話就又是長篇大論的了。基於這種考慮,這裏只是簡單的介紹一下AIDL這個東西,它的具體的語法,到底怎麼來實現IPC,我會單獨寫一篇博客來敘述這方面的東西。

1.2.4,Messenger與AIDL的比較

首先,在實現的難度上,確定是Messenger要簡單的多——至少不須要寫AIDL文件了(雖然若是認真的究其本質,會發現它的底層實現仍是AIDL)。另外,使用Messenger還有一個顯著的好處是它會把全部的請求排入隊列,所以你幾乎能夠不用擔憂多線程可能會帶來的問題。

可是這樣說來,難道AIDL進行IPC就一無可取了麼?固然不是,若是是那樣的話它早就被淘汰了。一方面是若是項目中有併發處理問題的需求,或者會有大量的併發請求,這個時候Messenger就不適用了——它的特性讓它只能串行的解決請求。另外,咱們在使用Messenger的時候只能經過Message來傳遞信息實現交互,可是在有些時候也許咱們須要直接跨進程調用服務端的方法,這個時候又怎麼辦呢?只能使用AIDL。

因此,這兩種IPC方式各有各的優勢和缺點,具體使用哪一種就看具體的須要了——固然,能使用簡單的就儘可能使用簡單的吧。

1.2.5,service的生命週期

當服務與全部客戶端之間的綁定所有取消時,Android 系統便會銷燬這個服務(除非還使用 onStartCommand() 啓動了該服務)。所以,若是服務是純粹的綁定服務,原則上咱們是無需對其生命週期進行管理的—Android 系統會根據它是否綁定到任何客戶端幫咱們管理。但實際上,咱們應該始終在完成與服務的交互時或 Activity 暫停時取消綁定,以便服務可以在未被佔用時關閉。

若是你只須要在 Activity 可見時與服務交互,則能夠在 onStart() 期間綁定,在 onStop() 期間取消綁定。若是你但願 Activity 在後臺中止運行狀態下仍可接收響應,則可在 onCreate() 期間綁定,在 onDestroy() 期間取消綁定。可是注意,這意味着你的 Activity 在其整個運行過程當中(甚至包括後臺運行期間)都須要使用服務,所以若是服務位於其餘進程內,那麼當你提升該進程的權重時,系統終止該進程的可能性會增長。一般狀況下,切勿在 Activity 的 onResume() 和 onPause() 期間綁定和取消綁定,由於每一次生命週期轉換都會發生這些回調,咱們應該使發生在這些轉換期間的處理保持在最低水平。此外,若是咱們的應用內的多個 Activity 綁定到同一服務,而且其中兩個 Activity 之間發生了轉換,則若是當前 Activity 在下一次綁定(恢復期間)以前取消綁定(暫停期間),系統可能會銷燬服務並重建服務。

此外,若是咱們的服務已啓動並接受綁定,則當系統調用 onUnbind() 方法時,若是咱們想在客戶端下一次綁定到服務時接收 onRebind() 調用(而不是接收 onBind() 調用),則可選擇返回 true。onRebind() 返回空值,但客戶端仍在其 onServiceConnected() 回調中接收 IBinder。下圖說明了這種生命週期的邏輯:
使用bindService()啓動的service的生命週期

2,何時用startService何時用bindService?

這個其實能夠經過它們的特色很輕鬆的獲得結論:它們之間的主要區別其實體如今兩點,可否交互,以及生命週期。因此很顯然的,startService適合那種啓動以後不顯式中止它就永遠在後臺運行,而且不須要客戶端與服務端交互的service。比方說一條專門拿來存數據到本地數據庫的service,它就一直在後臺等着有別的組件startService,而後把拿到的數據存入數據庫,這就顯然是用startService作的事情。而bindService呢,就適合那種能夠交互的,能夠掌控它何時停何時開始的。另外,若是有IPC的需求,那固然bindService是必不可少的了。

咱們在上一篇博文裏講過,其實在大多數狀況下,startService和bindService都是相輔相成的,它們並非孤立的存在。比方說我這個時候要作一個音樂播放器,那麼後臺播放是確定要的吧?總不能手機一熄屏音樂也沒了。另外,控制音樂也是要的吧?什麼上一首下一首播放暫停什麼的。這不就強強聯合了麼?固然要注意的是,在這兩種啓動方式同時存在去啓動一個service的時候,service的生命週期會發生變化,必須從兩種方法的角度看service均中止才能真正中止。附上一張圖:
兩種方式的生命週期

結語

有關「Android中的Service」的博文到這裏就差很少結束了。這兩篇基本上仍是比較完整的介紹了service的方方面面的東西,可是也僅限於介紹了——裏面的一些點若是講的話就太過於深刻了,這樣很容易致使博文失去重心,這是我回顧之前寫的一些博文獲得的經驗。之前有些文章,尤爲是源碼解析方面的,我喜歡尋根溯源刨根問底,因此會一直順着源碼往下挖掘,直到挖不動爲止。這樣的話,就我我的而言確定是能獲得不少收穫的,可是寫成博客以後可能會顯得比較的晦澀,由於必須順着個人思路,跟着我一路挖下去才能理解個人意思——而一篇好的博客應當是簡潔明瞭的,能讓觀者輕鬆地有所收穫的。

關於IBinder,Binder,Messenger,AIDL,IPC,等等等等,我後續會有一批關於它們的專題文章,在裏面會有比較詳細的講解,包括源碼解析之類的。

再次感謝Google官方文檔,感謝鴻洋大哥以及郭神等大神——本文有部份內容參考了他們的一些文章。