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

Taw5261 8年前發布 | 93K 次閱讀 Android Service Android開發 移動開發

前言

前面一篇博文介紹了關于Service的一些基本知識,包括service是什么,怎么創建一個service,創建了一個service之后如何啟動它等等。在這一篇博文里有一些需要前一篇鋪墊的東西,建議沒有看過前一篇博文的同學先去看一下前一篇: Android中的Service:默默的奉獻者 (1)

但是在前一篇博文中也有一些遺漏的東西——主要是關于bindService()這一塊的具體細節。由于這一塊涉及的東西還是比較多,所以在這里單獨提出來了。閑話不多說,進入正文。

1、bindService()

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

    這是一種比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");
}

};</code></pre>

上面的例子實現了一個比較普通的ServiceConnection的主要功能,我們可以通過它得到目標service的對象,然后可以調用其內的共有方法,實現客戶端與服務端交互的目的。

bindService()方法的第三個參數是一個int值,還叫flag(這flag立的),它是用來做什么的呢?它是一個指示綁定選項的標志,通常應該是 BIND_AUTO_CREATE,以便創建尚未激活的服務。 其他可能的值為 BIND_DEBUG_UNBIND 和 BIND_NOT_FOREGROUND,或 0(表示無)。

ok,客戶端的配置到這里就差不多搞定了,接下來看看服務端需要做些什么。

1.2、服務端的配置

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

可以看到,這里提出了一個IBinder接口的概念。那么這個IBinder接口是什么呢?它是一個在整個Android系統中都非常重要的東西,是為高性能而設計的輕量級遠程調用機制的核心部分。當然,它不僅僅可以用于遠程調用,也可以用于進程內調用——事實上,我們現在所說的service這里的IBinder既有可能出現遠程調用的場景,比如用它來進行IPC,也有可能出現進程內調用的場景,比如用它來進行同進程內客戶端與服務器的交互。IBinder的具體的工作原理在這里就不詳述了,以后我應該會就這一塊的內容單獨寫一系列的博客,在這里只需要知道它是客戶端用來和服務器進行交互的接口,并且知道可以怎樣通過IBinder來實現它們的交互就可以了。

一般來講,我們有三種方式可以獲得IBinder的對象:繼承Binder類,使用Messenger類,使用AIDL。接下來我將就這三種方式展開來講。

1.2.1、繼承Binder類

看到這里可能有些同學會問:Binder又是什么?在這里我并不準備給出一個詳盡、確切的答案——因為他太復雜了,深入的講下去的話必將導致這篇博文失去重心。在這里我們只需要知道,它實現了IBinder接口,通過實現Binder類,我們的客戶端可以直接通過這個類調用服務端的公有方法。另外,雖然從IPC的角度來講,Binder是Android中的一種跨進程通信方式,但是其實一般service里面的Binder是不會涉及進程間通信的,所以其在這種情況下顯得較為簡單。

下面我們來看下通過繼承Binder類實現客戶端與服務端通信應該怎樣做:

  • 在service類中,創建一個滿足以下任一要求的Binder實例:
    • 包含客戶端可調用的公共方法
    • 返回當前Service實例,其中包含客戶端可調用的公共方法
    • 返回由當前service承載的其他類的實例,其中包含客戶端可調用的公共方法
    </li>
  • 在onBind()方法中返回這個Binder實例
  • 在客戶端中通過onServiceDisconnected()方法接收傳過去的Binder實例,并通過它提供的方法進行后續操作
  • </ul>

    可以看到,在使用這種方法進行客戶端與服務端之間的交互是需要有一個強制類型轉換的——在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);
    }
    

    }</code></pre>

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

    }</code></pre>

    當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();
    }
    

    }</code></pre>

    服務端主要是返給客戶端一個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;
        }
    }
    

    }</code></pre>

    客戶端就主要是發起與服務端的綁定,以及通過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官方文檔,感謝鴻洋大哥以及郭神等大神——本文有部分內容參考了他們的一些文章。

     

    系列文章

    Android中的Service:默默的奉獻者 (1)

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

     

    來自:http://blog.csdn.net/luoyanglizi/article/details/51594016

     

 本文由用戶 Taw5261 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!