天天看點

Service與Android系統設計

共18次連載,講述Android Service背後的實作原理,透析Binder相關的RPC。

有了AIDL定義之後,我們便可實作這這一接口類的定義,主要是提供一個Stub類的實作,在這個Stub對象裡,提供getPid(void)的具體實作。

    <b>private final</b>ITaskService.Stub mTaskServiceBinder =<b>new</b> ITaskService.Stub() {

       <b>public int</b> getPid() {

           <b>return</b> Process.myPid();

       }

    };

于 是,當應用程式通過某種方式可以取得ITaskServiceStub所對應的IBinder對象之後,就可以在自己的程序裡調用IBinder對象的 getPid(),但這一方法實際上會是在别一個程序裡執行。雖然我們對于執行IBinder的Stub端代碼的執行環境并沒有嚴格要求,可以在一個運作 中的程序或是線程裡建立一個Stub對象就可以了,但出于通用性設計的角度考慮,實際上我們會使用Service類來承載這個Stub對象,通過這樣的方 式,Stub的運作周期便被Service的生存周期所管理起來。這樣實作的原因,我們可以回顧我們前面所描述的功耗控制部分。當然,使用Service 帶來的另一個好處是代碼的通用性更好,我們在後面将感受到這種統一化設計的好處。

于是,對應于.aidl的具體實作,我們一般會使用一個 Service對象把這個Stub對象的存活周期“包”起來。我們可以通過在Service裡使用一個 final類型的Stub對象,也可以通過在onBind()接口裡進行建立。這兩種不同實作的結果是,使final類型的Stub對象,類似于我們的 Singleton設計模式,用戶端通路過來的總會是由同一個Stub對象來處理;而在onBind()接口裡建立新的Stub,可以讓我們對每個用戶端 的通路都建立一個Stub對象。出于簡化設計的考慮,我們一般會使用final的唯一Stub對象,于是我們得到的完整的Service實作如下:

<b>package</b> org.lianlab.services;

<b>import</b> android.app.Service;

<b>import</b> android.content.Intent;

<b>import</b> android.os.IBinder;

<b>import</b> android.os.Process;

<b>public class</b> TaskService <b>extends</b> Service {1

    @Override

    <b>public</b> IBinderonBind(Intent intent) {

       <b>if</b> (ITaskService.<b>class</b>.getName().equals(intent.getAction())) {2

           <b>return</b> mTaskServiceBinder;

        <b>return null</b>;

    }

    <b>private final</b>ITaskService.Stub mTaskServiceBinder =<b>new</b> ITaskService.Stub() {  3

       <b>public int</b> getPid() { 4

}

1 由于實作上的靈活性,我們一般使用Service來承載一個AIDL的Stub對象,于是這個Stub的存活周期,會由Service的編寫方式決定。當 我們的Stub對象是在onBind()裡傳回時,Stub對象的存活周期是Service處于Bounded的周期内;如果使用final限定,則 Stub對象的存活周期是Service在onCreate()到onDestroy()之間

2 用于處理bindService()發出Intent請求時的Action比對,這一行一般會在AndroidManifest.xml檔案裡對 Service對Intent filter設定時使用。我們這種寫法,則使這一Service隻會對Intent裡Action屬性是 “org.lianlab.services.ITaskService”的bindService()請求作出響應。這一部分我們在後面的使用這一 Service的Activity裡可以看到

3 建立ITaskService.Stub對象,并實作Stub對象所要求的方法,也就是AIDL的實作。Stub對象可以像我們這樣靜态建立,也可以在 onBind()裡動态建立,但必須保證建立在onBind()傳回之前完成。在onBind()回調之後,實際上在用戶端則已經發生了 onServiceConnected()回調,會造成執行出錯。

4 實作,這時我們最終給用戶端提供的遠端調用,就可以在Stub對象裡實作。我們可以實作超出AIDL定義的部分,但隻有AIDL裡定義過的方法才會通過Binder暴露出來,而AIDL裡定義的接口方法,則必須完整實作。

       有了這樣的定義之後,我們還需要在AndroidManifest.xml檔案裡将Service聲明出來,讓系統裡其他部分可以調用到:

    &lt;application

      …

       &lt;service android:name=".TaskService"&gt;

           &lt;intent-filter&gt;

                &lt;action android:name="org.lianlab.services.ITaskService"/&gt;

          &lt;/intent-filter&gt;

       &lt;/service&gt;

&lt;/application&gt;

在 AndroidManifest.xml檔案裡,會在&lt;application&gt;标簽裡通過&lt;service&gt;标簽來申明這一應 用程式存在某個Service實作,在service命名裡,如果service的名稱與application名稱的包名不符,我們還可以使用完整的 “包名+類名”這樣命名方式來向系統裡注冊特殊的Service。在&lt;service&gt;标簽裡,我們也可以注冊&lt;intent- filter&gt;來标明自己僅接收某一類的Intent請求,比如我們例子裡的action會比對 “org.lianlab.services.ITaskService”,如果沒有這樣的intent-filter,則任何以 TaskService(ComponentName的值是”org.lianlab.services.ITaskService”)為目标的 Intent請求會觸發TaskService的onBind()回調。當然,我們在&lt;service&gt;标簽内還可以定義一些權限,像我們例 子裡的這個TaskService是沒有任何權限限制的。有了這個AndroidManifest.xml檔案,我們再來看看用戶端的寫法。

在 用戶端進行通路時,由于它必須也使用同一接口類的定義,于是我們可以直接将同一.aidl檔案拷貝到用戶端應用程式的源代碼裡,讓這些接口類的定義在客戶 端代碼裡也可以被自動生成,然後用戶端便可以自動得到Proxy端的代碼。因為我們這裡使用了Service,又是通過onBind()傳回 IBinder的對象引用,這時用戶端在使用IBinder之前,需要通過bindService()來觸發Service端的onBind()回調事 件,這時會通過用戶端的onServiceConnected()回調将Stub所對應的Binder對象傳回。我們在稍後看AIDL的底層實作時會發 現,在此時用戶端的Binder對象隻是底層Binder IPC的引用,此時我們還需要建立一個基于這一Stub接口的Proxy,于是在用戶端會需要調用asInterface()建立Proxy對象,這一 Proxy對象被轉義成具體的Service,在我們的例子裡,用戶端此時就得到了ITaskService對象。從這時開始,在用戶端裡通過 ITaskService.getPid()的調用,都會通過Binder IPC将操作請求發送到Service端的Stub實作。于是,我們可以得到用戶端的代碼,在Android裡我們一般用Activity來完成這樣的操 作,如下代碼所示:

<b>package</b> org.lianlab.hello;

<b>import</b> android.os.Bundle;

<b>import</b> android.os.RemoteException;

<b>import</b> android.app.Activity;

<b>import</b> android.content.ComponentName;

<b>import</b> android.content.Context;

<b>import</b> android.content.ServiceConnection;

<b>import</b> android.util.Log;

<b>import</b> android.view.View;

<b>import</b> android.view.View.OnClickListener;

<b>import</b> android.widget.TextView;

<b>import</b> org.lianlab.services.ITaskService;1

<b>import</b> org.lianlab.hello.R;

<b>public class</b> Helloworld <b>extends</b> Activity

{

    /** Called when the activity is first created.*/

    ITaskService mTaskService = <b>null</b>; 2

    <b>public void</b>onCreate(Bundle savedInstanceState)

    {

       <b>super</b>.onCreate(savedInstanceState);

       setContentView(R.layout.main);

           bindService(<b>new</b> Intent(ITaskService.<b>class</b>.getName()),mTaskConnection,

                    Context.BIND_AUTO_CREATE);   3

       ((TextView) findViewById(R.id.textView1)).setOnClickListener(

                <b>new</b> OnClickListener() {

                   @Override

                   <b>public void</b> onClick(Viewv) {

                       <b>if</b> (mTaskService !=<b>null</b>) {

                           <b>try</b> {      4

                               <b>int</b> mPid = -1;

                               mPid = mTaskService.getPid();  

                               Log.v("get Pid "," = " +mPid);

                               ((TextView)findViewById(R.id.textView1)).setText("Servicepid is" + mPid);

                           } <b>catch</b>(RemoteException e) {

                              e.printStackTrace();

                          }

                       }

                       <b>else</b> {

                           ((TextView)findViewById(R.id.textView1)).setText("Noservice connected");

                   }

          });

    <b>private</b>ServiceConnectionmTaskConnection =<b>new</b> ServiceConnection() {  5

       <b>public void</b> onServiceConnected(ComponentName className, IBinder service) {

           mTaskService = ITaskService.Stub.asInterface(service);  6

       <b>public void</b> onServiceDisconnected(ComponentName className) {

           mTaskService = <b>null</b>;

};

   @Override

    <b>public void</b> onDestroy()

       <b>super</b>.onDestroy();

       <b>if</b> (mTaskService !=<b>null</b>) {

           unbindService(mTaskConnection);  7

1 必須導入遠端接口類的定義。我們必須先導入類或者函數定義,然後才能使用,對于任何程式設計語言都是這樣。但我們在AIDL程式設計的環境下,實際這一步驟變得更 加簡單,我們并非需要将Service實作的源檔案拷貝到應用程式源代碼裡,也是隻需要一個AIDL檔案即可,這一檔案會自動生成我們所需要的接口類定 義。是以可以注意,我們導入的并非Service實作的”org.lianlab.services.TaskService”,而AIDL接口類 的”org.lianlab.services.ITaskService”。這種方式更靈活,同時我們在AIDL環境下還得到了另一個好處,那就是可以 隐藏實作。

2 對于用戶端來說,它并不知道TaskService的實作,于是我們統一使用ITaskService來處理對遠端對象的引用。跟步驟1對應,這時我們會使用ITaskService來通路遠端對象,就是我們的mTaskService。

3 我們必須先建立對象,才能調用對象裡的方法,對于AIDL程式設計而言,所謂的建立對象,就是通過bindService()來觸發另一個程序空間的Stub 對象被建立。bindService()的第一參數是一個Intent,這一Intent裡可以通過ComponentName來指定使用哪個 Service,但此時我們會需要Service的定義,于是在AIDL程式設計裡這一Intent會變通為使用ITaskService作為Action 值,這種小技巧将使bindService()操作會通過IntentFilter,幫我們找到合适的目标Service并将其綁定。 bindService()的第二個參數的類型是ServiceConnection對象,bindService()成功将使用這樣一個 ServiceConnection對象來管理onServiceConnected()與onServiceDisconnected()兩個回調,于 是一般我們會定義一個私有的ServiceConnection對象來作為這一參數,見5。最後的第三個參數是一個整形的标志,說明如何處理 bindService()請求,我們這裡使用Context.BIND_AUTO_CREATE,則發生bindService()操作時,如果目标 Service不存在,會觸發Service的onCreate()方法建立。

4 我們這裡使用onClickListener對象來觸發遠端操作,當使用者點選時,就會嘗試去執行mTaskService.getPid()方法。正如我 們看到的,getPid()是一個RPC方法,會在另一個程序裡執行,而Exception是無法跨程序捕捉的,如果我們希望在進行方法調用時捕捉執行過 程裡的異常,我們就可以通過一個RemoteException來完成。RemoteException實際上跟方法的執行上下文沒有關系,也并非完整的 Exception棧,但還是能幫助我們分析出錯現場,是以一般在進行遠端調用的部分,我們都會try來執行遠端方法然後捕捉 RemoteException。

5 如3所述,bindService()會使用一個ServiceConnection對象來判斷和處理是否連接配接正常,于是我們建立這麼一個對象。因為這個 私有ServiceConnection對象是作為屬性存在的,是以實際上在HelloActivity對象的初始化方法裡便會被建立。

6 在onServiceConnected()這一回調方法裡,将傳回引用到遠端對象的IBinder引用。在Android官方的介紹裡,說是這一 IBinder對象需要通過asInterface()來進行類型轉換,将IBinder再轉換成ITaskService。但在實作上并非如此,我們的 Proxy在内部是被拆分成Proxy實作與Stub實作的,這兩個實作都使用同一IBinder接口,我們在onServiceConnected() 裡取回的就是這一對象IBinder引用,asInterface()實際上的操作是通過IBinder對象,得到其對應的Proxy實作。

7 通過bindService()方法來通路Service,則Service的生存周期位于bindService()與unbindService() 之間的Bounded區域,是以在bindService()之後,如果不調用unbindService()則會造成記憶體洩漏,Binder相關的資源 無法得到回收。是以在合适的點調用unbindService()是一種好習慣。

通過這樣的方式,我們就得到了耦合度很低的Service 方案,我們的這個Activity它即可以與Service處于同一應用程式程序,也可以從另一個程序進行跨程序調用來通路這一Service裡暴露出來 的方法。而在這種執行環境的變動過程中,代碼完全不需要作Service處理上的變動,或者說Activity本身并不知道AIDL實作上的細節,在同一 個程序裡還是不在同一程序裡。

這種方式很簡單易行,實際上在程式設計上,AIDL在程式設計上帶來的額外編碼上的開銷非常小,但得到了靈活性設計的RPC調用。

接下來,我們看一下,AIDL幫我們完成了什麼。

 本文轉自 21cnbao 51CTO部落格,原文連結:http://blog.51cto.com/21cnbao/1031112,如需轉載請自行聯系原作者

繼續閱讀