Service與Android系統實現(1)-- 應用程序裏的Service

Service

Service在Android應用程序裏四大實體之一。Android的應用程序不光是需要有圖形界面來進行交互,有時也會需要在沒有交互的情況下進行的操作,比如下載、更新、監聽等。比如目前對我們網絡生存影響如此之大的社交網絡、或是更老一些聊天工具,總需要這類應用程序可以一直在後臺運行,以等待可能過來的消息。:

Service擁有一部分Activity所無法完成的能力。一是後臺運行,有時我們並不希望有過多對話框來影響用戶體驗,開機自動啓動,便可默默地在後臺運行。另一特性,就是不被Activity生命週期所管理,Activity處於完全活躍的週期是onResume()與onPause()之間,如果這週期之外發生了事件,實際上Activity構成的執行部分也不會被執行到,從而無法響應處理,但Service由於本身過於簡單,則會通過一定的輔助手段來達到這個目標。

從Android應用程序的設計原理來看,Service同樣也是主線程裏執行的(這點尤爲重要,Service由於在主線程裏執行,於是也會因爲執行耗時操作而觸發ANR)。一個應用程序既然可以通過拓展Activity得到可交互的代碼邏輯,同樣也可以通過拓展Service來得到不交互在後臺執行的邏輯。如下圖加強部分所示:


Activity對應用程序來說是最重要的組件,但從Android系統設計的角度來看,Service對系統層實現來說才最重要的。Service是構建系統的根本,支持整個系統運營的環境framework,本身就是由大量Service來構成的。也就是說,Service反而是構建Activity的基礎環境。

Android與其他系統設計最大的不同之處在於,它並不是一種傳統的系統環境,而是一種更加開放的系統。傳統的圖形操作系統裏,會有基本環境,會有系統管理組件,應用程序只是作爲補充性功能實現。但Android並不如此,Android系統是沒有應用程序的,達到了“無邊界”系統設計的最高境界,“手裏無劍,心中有劍”。整個Android系統的設計使自己扮演着支撐系統的運行環境的角色,不再有基本系統的概念,而變成了一種“有或者無”的應用程序的支撐環境,沒有系統組件的概念。而我們所謂的系統應用程序,我們只能稱它們爲“內置”應用程序,只是服務於黑心移動運營商的一種方式而已。

這種設計的精髓在於,系統本身不會處理交互,而只是提供交互的手段。從前面我們對於應用程序運行環境的分析中,我們可以看到,Android的Framework,提供一部分功能供應用程序調用,而除了這些應用程序直接使用的API實現,其他代碼邏輯就會全是由Service構成。當然作爲系統實現角度的Service,與應用程序編程裏實現的Service是有差別的,更強調共享,但基本構架一樣。在過渡到Android系統的解析之前,我們先從應用程序的Service概念入手。

本地簡單Service

我們先來在應用程序裏寫一個簡單的Service。打開Eclipse,新建一個Android工程,然後再創建一個新的基於Service基類的類。與Activity的編程方式類似,Service在編程上也是基於回調方式實現的,我們繼承基類Service之後所需要做的,就是通過IoC模式替換原來的Service回調的實現:

[java] view plaincopy
  1. import android.app.Service;  
  2. import android.content.Intent;  
  3. import android.os.IBinder;  
  4. import android.util.Log;  
  5. public class LianlabServiceextends Service  
  6. {  
  7.     private staticfinal String TAG ="LianlabService";  
  8.    @Override  
  9.     public void onCreate() {  
  10.        super.onCreate();  
  11.        Log.v(TAG, "inonCreate()");  
  12.     }  
  13.    @Override  
  14.     public int onStartCommand(Intent intent,int flags,int startId) {  
  15.        super.onStartCommand(intent, flags, startId);  
  16.        Log.v(TAG, "inonStartCommand()");  
  17.        return START_STICKY;  
  18.     }  
  19.    @Override  
  20.     public void onDestroy()  
  21.     {  
  22.        Log.v(TAG, "inonDestroy().");  
  23.        super.onDestroy();  
  24.     }  
  25. }  

有了Service的具體實現之後,系統並不會自動地識別到這一實現,在Android世界裏,一切都通過AndroidManifest.xml來驅動,於是,我們還需要修改AndroidManifest.xml文件,加入Service的定義:

[html] view plaincopy
  1. <applicationandroid:labelapplicationandroid:label="@string/app_name">  
  2.   
  3.    <serviceandroid:nameserviceandroid:name=".LianLabService"/>  
  4. ;/application>   


在上面這種方式裏實現的Service,可被執行的方式很有限,就是提供一個可執行的線程環境,可以被Intent所驅動,執行onStartCommand()回調。功能有限並不代表無能,在Android系統裏,我們可能還經常會碰到這樣的需求:比如我們使用GPS裏來記錄我們行動軌跡時,這時我們很可能需要通過後臺的執行的代碼來定時檢查GPS的定位信息;殺毒或是監控軟件可能希望駐留在後臺,並可被Intent來驅動開始進行殺毒;我們的聊天或是社交應用,需要在後臺定時地與服務發送“心跳”(Heart beat),用來標識自己的在線狀態等。這樣的例子,大家可以回頭到我們畫的GPS軌跡跟蹤的構成示意圖,這樣的跟蹤軟件,必須是通過一個接收啓動完成信息的Broadcast Receiver來監聽自己是否應該被執行,而接收到到啓動完成的Broadcast Intent之後,則必須觸發一直在後臺運行的TrackerService的執行。

既然我們在上述方式裏實現的Service是由Intent驅動的,於是我們的使用這一Service部分的代碼也會很簡單。在任何可被執行到的代碼裏使用startService(Intent)就可以完成,我們可以給某個控件註冊點擊事件支持onClickListener對象,然後覆蓋onClick()回調方法:

[java] view plaincopy
  1.  public void onClick(Viewv) {  
  2.       Intent intent = new Intent(this,  
  3.       LianlabService.class);  
  4.     startService(intent);  
  5. }    


我們這裏既然使用到了Intent,也就是說我們還可以通過extras這個Bundle對象給我們這裏實現的LianLabService來傳遞運行的參數。於是,這時我們的代碼貌似有了pthread多線程執行效果,通過傳參,然後我們會執行一個在另一線程裏運行的函數,只是函數是固定的onStartCommand()回調方法。但這只是貌似,並非實際情況,Service的執行與後臺線程方式極大不同,Service只是一種代碼邏輯的抽象,實際上它還是運行在Activity同一線程上下文環境。


於是,我們並不能用Service來進行任何耗時操作,否則會阻塞主線程而造成應用程序的無法響應錯誤,也就是臭名昭著的ANR錯誤。Service僅能用於不需要界面交互的代碼邏輯。

本地 Bounded Service

這種使用Intent來驅動執行的Service,可用性有限,並不能完全滿足我們對於後臺服務的需求。對於後臺執行的代碼,我們更多的應用情境不光是希望進行後臺操作,我們可能還希望能進行交互,可以隨時檢查後臺操作的結果,並能暫停或是重啓後臺執行的服務,可以在使用某一Service時保證它並不會退出執行,甚至一些提交一些參數到後臺來進行復雜的處理。這時,我們可以使用Service的另一個訪問方式,使用Binder接口來訪問。我們的Service基類還提供這類應用的回調方式,onBind()、onUnbind()和onRebind()。使用Binder來訪問Service的方式比Intent驅動的應用情境更底層,onBind()回調主要用於返回一個IBinder對象,而這一IBinder對象是Service的引用,將會被調用端用於直接調用這一Service裏實現的某些方法。

同樣的Service實現,如果通過IBinder來驅動,則會變成下面的樣子:

[java] view plaincopy
  1. import android.app.Service;  
  2. import android.content.Intent;  
  3. import android.os.IBinder;  
  4. import android.util.Log;  
  5. public class LianlabServiceextends Service  
  6. {  
  7.     private staticfinal String TAG ="LianlabService";  
  8.    
  9.    @Override  
  10.     public void onCreate() {  
  11.        super.onCreate();  
  12.        Log.v(TAG, "inonCreate()");  
  13.     }  
  14.    @Override  
  15.     public intonStartCommand(Intent intent,int flags,int startId) {  
  16.        super.onStartCommand(intent, flags, startId);  
  17.        Log.v(TAG, "in onStartCommand()");  
  18.        return START_STICKY;  
  19.     }  
  20.    @Override  
  21.     public void onDestroy()  
  22.     {  
  23.        Log.v(TAG, "inonDestroy().");  
  24.       super.onDestroy();  
  25. }  
  26.    finalIService.Stub m_binder =newIService.Stub() {  
  27.         ...  
  28.    }  
  29.    @Override  
  30.     public IBinderonBind(Intent intent) {  
  31.        Log.v(TAG, "inonBind().");  
  32.        return mBinder;  
  33.     }  
  34.    @Override  
  35.     public booleanonUnbind(Intent intent) {  
  36.        Log.v(TAG, "inonUnbind().");        
  37.        return mAllowRebind;  
  38.     }  
  39.    @Override  
  40.     public void onRebind(Intentintent) {  
  41.        Log.v(TAG, "inonRebind().");  
  42.     }  
  43. }  

使用IBinder對象來觸發的Service,在訪問時的代碼實現則變得完全不樣了。比如我們同樣通過onClick()來操作後臺的某些操作,但這時並非通過Intent來完成,而是直接使用某個引用這一Service的IBinder對象來直接調用Service裏實現的方法。

[java] view plaincopy
  1. bindService(intent, m_connection, …);  
  2. private ServiceConnection m_connection =new ServiceConnection() {  
  3.     private IService onServiceConnected(…, IBinder service) {  
  4.          m_service =IService.Stub.asInterface(service);  
  5.      }  
  6. }  

如果Service裏實現了某些方法,比如kill(),在上述代碼之後,我們對Service的驅動則會變成代碼上的直接調用。在onServiceConnected()回調方法被觸發之後,我們始終都可以通過m_service.kill()來訪問Service裏的kill()方法。而bindService()這方法的調用,則會觸發onServiceConnected()事件。

       這樣就要讓人抓狂了,既然如此麻煩,何不直接調用呢?所以,事實上,這裏列舉的這種代碼實現方式,在現實編程裏確實不常用。一般而言,如果Service通過IBinder對象來觸發,那隻會出於一個理由,提供一種可能性,將來可以更靈活地提供給另一進程來訪問,這就是我們稍後會說明的Remote Service。

這兩種不同的Service的實現方式,將決定Service的不同被調用方式,或者準確地說,將決定Service的不同生命週期。

如圖所示,Service的進程存活期是處理onCreate()與onDestroy()回調方法之間。onStartCommand()回調是不受控的,每次Intent都將觸發執行一次, onStartCommand()執行完則會退出;而使用onBind()來驅動的Service,其活躍區間是onBind()與onUnbind()之間,其活躍週期始終在控制範圍內。

Remote Service

得益於Android的進程間模型,無論是系統實現端的開發,還是應用程序的開發者,可認爲自己的代碼都將在一種安全的環境下執行。但對於在需要共享的場合,又帶來了不方便之處,我們不再可以在一個進程裏很方便地調用到另一進程裏的實現。在上面兩種Service實現裏,基本上只能自已服務自己,而不能將功能共享給更多地使用者。於是,Android在設計初期,便引入了另一個概念,Remote Service。

Remote Service定義瞭如何從一個進程,直接訪問到另一個進程裏實現的方法,這樣的機制在通用編程領域被稱爲RPC,Remote Procedure Call,在Android環境裏,因爲這樣的RPC與Service的概念揉合到了一起,所以被稱爲Remote Service。

如下圖所示,Remote Service基本上算是在Bounded Service實現上的一種拓展。調用端(一般是與用戶交互的Activity)會通過bindService()發一個Intent到Service進程,而Service進程會在onBind()回調方法裏返回自己的Service處理接口。當調用端得到相應的處理接口,也就是下圖所示的Stub.Proxy對象之後,就可以調用遠端Stub對象裏實現的方法。


當然,由於這種方式需要在每一次調用時都進行一次遠程調用,於是實現起來並不簡單,甚至可以說是會很麻煩。在上圖邏輯裏要實現一個Remote Service,在這一Remote Service裏實現一個方法,就需要加入多個對象。於是,在Android裏引入了AIDL,可以通過AIDL來簡化通用代碼的處理,在上圖中Stub對象、Stub.Proxy對象都將由aidl自動管理。

實現一個Remote Service,基本上可以分三步完成:

  1.  定義aidl
  2.  實現被調用的Remote Service(提供可實例化的Stub對象)
  3.  調用Remote Service

定義AIDL

引入了AIDL文件之後,在編碼時就會更加簡潔,比如加入一個接口類ITaskService,而在ITaskService裏提供一個getPid()方法:
[java] view plaincopy
  1. packageorg.lianlab.service;  
  2. interface ITaskService {  
  3.     int getPid ( );  
  4. }  

實現Remote Service

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

[java] view plaincopy
  1. private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {  
  2.    public int getPid() {  
  3.        return Process.myPid();  
  4.    }  
  5. };  

於是,當應用程序通過某種方式可以取得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實現如下:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. import android.app.Service;  
  3. import android.content.Intent;  
  4. import android.os.IBinder;  
  5. import android.os.Process;  
  6. public class TaskService extends Service {1  
  7.     @Override  
  8.     public IBinderonBind(Intent intent) {  
  9.        if (ITaskService.class.getName().equals(intent.getAction())) {2  
  10.            return mTaskServiceBinder;  
  11.        }  
  12.         return null;  
  13.     }  
  14.    
  15.     private finalITaskService.Stub mTaskServiceBinder =new ITaskService.Stub() {  3  
  16.        public int getPid() { 4  
  17.            return Process.myPid();  
  18.        }  
  19.     };  
  20. }  

  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聲明出來,讓系統裏其他部分可以調用到:

[html] view plaincopy
  1. <application  
  2.       …  
  3.        <service android:name=".TaskService">  
  4.            <intent-filter>  
  5.                 <action android:name="org.lianlab.services.ITaskService"/>  
  6.           </intent-filter>  
  7.        </service>  
  8. </application>  

在AndroidManifest.xml文件裏,會在<application>標籤裏通過<service>標籤來申明這一應用程序存在某個Service實現,在service命名裏,如果service的名稱與application名稱的包名不符,我們還可以使用完整的“包名+類名”這樣命名方式來向系統裏註冊特殊的Service。在<service>標籤裏,我們也可以註冊<intent-filter>來標明自己僅接收某一類的Intent請求,比如我們例子裏的action會匹配“org.lianlab.services.ITaskService”,如果沒有這樣的intent-filter,則任何以TaskService(ComponentName的值是”org.lianlab.services.ITaskService”)爲目標的Intent請求會觸發TaskService的onBind()回調。當然,我們在<service>標籤內還可以定義一些權限,像我們例子裏的這個TaskService是沒有任何權限限制的。有了這個AndroidManifest.xml文件,我們再來看看客戶端的寫法。

訪問Remote Service

在客戶端進行訪問時,由於它必須也使用同一接口類的定義,於是我們可以直接將同一.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來完成這樣的操作,如下代碼所示:

[java] view plaincopy
  1. package org.lianlab.hello;  
  2. import android.os.Bundle;  
  3. import android.os.IBinder;  
  4. import android.os.RemoteException;  
  5. import android.app.Activity;  
  6. import android.content.ComponentName;  
  7. import android.content.Context;  
  8. import android.content.Intent;  
  9. import android.content.ServiceConnection;  
  10. import android.util.Log;  
  11. import android.view.View;  
  12. import android.view.View.OnClickListener;  
  13.    
  14. import android.widget.TextView;  
  15.    
  16. import org.lianlab.services.ITaskService;1  
  17. import org.lianlab.hello.R;  
  18.    
  19. public class Helloworld extends Activity  
  20. {  
  21.     /** Called when the activity is first created.*/  
  22.    
  23.     ITaskService mTaskService = null2  
  24.    
  25.     @Override  
  26.     public voidonCreate(Bundle savedInstanceState)  
  27.     {  
  28.        super.onCreate(savedInstanceState);  
  29.        setContentView(R.layout.main);  
  30.    
  31.            bindService(new Intent(ITaskService.class.getName()),mTaskConnection,  
  32.                     Context.BIND_AUTO_CREATE);   3  
  33.          
  34.        ((TextView) findViewById(R.id.textView1)).setOnClickListener(  
  35.                 new OnClickListener() {  
  36.                    @Override  
  37.                    public void onClick(Viewv) {  
  38.                        if (mTaskService !=null) {  
  39.                            try {      4  
  40.                                int mPid = -1;  
  41.                                mPid = mTaskService.getPid();    
  42.                                Log.v("get Pid "," = " +mPid);  
  43.                                ((TextView)findViewById(R.id.textView1)).setText("Servicepid is" + mPid);  
  44.                            } catch(RemoteException e) {  
  45.                               e.printStackTrace();  
  46.                           }  
  47.                        }  
  48.                        else {  
  49.                            ((TextView)findViewById(R.id.textView1)).setText("Noservice connected");  
  50.                        }  
  51.                    }  
  52.           });  
  53.    
  54.     }  
  55.    
  56.     privateServiceConnectionmTaskConnection = new ServiceConnection() {  5  
  57.        public void onServiceConnected(ComponentName className, IBinder service) {  
  58.            mTaskService = ITaskService.Stub.asInterface(service);  6  
  59.        }  
  60.    
  61.        public void onServiceDisconnected(ComponentName className) {  
  62.            mTaskService = null;  
  63.        }  
  64. };  
  65.    
  66.    @Override  
  67.     public void onDestroy()  
  68.     {  
  69.        super.onDestroy();  
  70.        if (mTaskService !=null) {  
  71.            unbindService(mTaskConnection);  7  
  72.        }  
  73.     }  
  74.      
  75. }  

  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調用。


雙向Remote Service

在AIDL編程環境裏實際上是支持反向調用的,原理跟我們實現一個Remote Service一樣,就是通過把Proxy與Stub反過來,就得到了這樣的回調式的aidl編程。唯一的區別是,當我們的Stub在Activity時實現時,我們實際上跟後臺線程執行也沒有區別,Callback並非是在主線程裏執行的,於是不能進行重繪界面的工作。於是,我們必須像後臺線程編程一樣,使用Handler來處理界面顯示處理。

定義AIDL

前面我們說過aidl是可以互相引用的,於是我們可以借用這樣的機制,通過引用另一個新增的aidl文件來加強我們前面的單向的TaskService版本。我們先增加一個新的ITaskServiceCallback.aidl文件,與ITaskService保持同一目錄:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. onewayinterface ITaskServiceCallback {  
  3.    void valueCounted(int value);  
  4. }  

在這一定義裏,我們新增加了一個ITaskServiceCallback的接口類,基本上與我們前面的ITaskService.aidl一樣,在這個接口類裏,我們新加了一個valueCounted()方法,這一方法將會被Service所使用。

在這個文件裏,唯一與ITaskService.aidl不同之處在於,我們使用了一個oneway的標識符,oneway可以使aidl調用具有異步調用的效果。在默認情況下,基於aidl的調用都會等待遠端調用完成之後再繼續往下執行,但有時我們可能希望在跨進程調用會有異步執行的能力,我們在發出調用請求後會立即返回繼續執行,調用請求的結果會通過其他的callback返回,或是我們乾脆並不在乎成功與否,此時就可以使用oneway。當然,從我們前面分析aidl底層進行的工作,我們可以知道,所謂的遠程調用,只不過是通過Binder發送出去一個命令而已,所以在aidl裏面如果使用了oneway限定符,也就是發送了命令就收工。

然後,我們修改一下我們的ITaskService.aidl,使我們可以使用上這個新加入的回調接口:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. import org.lianlab.services.ITaskServiceCallback;  
  3. interface ITaskService {  
  4.     intgetPid (ITaskServiceCallback callback);  
  5. }  

在Service端調用回調方法

我們會引用前面定義好的ITaskServiceCallback.aidl文件,通過包名+接口的方式進行引用。爲了省事,我們直接在原來的getPid()方法裏進行修改,將新定義的ITaskServiceCallback接口類作爲參數傳遞給getPid()接口。於是,在Service端Stub對象裏實現的getPid()方法,將可以使用這一回調對象:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. import android.app.Service;  
  3. import android.content.Intent;  
  4. import android.os.IBinder;  
  5. import android.os.Process;  
  6. import android.os.RemoteException;  
  7.    
  8. public class TaskService extends Service {  
  9.    
  10.    static private int mCount = 0;  
  11.      
  12.    @Override  
  13.    public IBinder onBind(Intent intent) {  
  14.        if (ITaskService.class.getName().equals(intent.getAction())) {  
  15.            return mTaskServiceBinder;  
  16.        }  
  17.        return null;  
  18.     }  
  19.    
  20.    private final ITaskService.Stub mTaskServiceBinder = newITaskService.Stub() {  
  21.        public int getPid(ITaskServiceCallback callback) { 1  
  22.            mCount ++ ;     2  
  23.            try {   3  
  24.                 callback.valueCounted(mCount);    4  
  25.            } catch (RemoteException e) {  
  26.                 e.printStackTrace();  
  27.            }  
  28.            return Process.myPid();  
  29.        }  
  30.     };  
  31. }  

加入了回調之後的代碼結構並沒有大變,只增加了3部分的內容,通過這三部分的內容,我們此時便可以記錄我們的getPid()總共被調用了多少次。

  1. getPid()方法,是通過aidl定義來實現的,否則會報錯。所以我們這裏新的getPid()會按照aidl裏的定義加入ITaskServiceCallback對象作爲參數,與ITaskService對象相反,這一對象實際上是由客戶端提供給Service端調用的。
  2. 爲了記錄下getPid()被調用了多少次,我們使用了一個mCount來進行計數,這一int爲static類型,於是在Service生存週期裏會始終有效。但這部分的改動與我們的回調改進並無直接關係。
  3. 在使用回調接口ITaskServiceCall之前,因爲這是一個遠程引用,我們會需要捕捉Remote Exception,由客戶端拋出的異常將在這裏被捕獲處理。
  4. 調用ITaskServiceCall裏定義的回調方法,將處理髮送給客戶端。此時,因爲是oneway,這時很多就會從回調方法裏返回,繼續執行原來的getPid(),再將處理結果以返回值的形式發送回客戶端。

加入了回調之後,對Service端的實現並沒有增加多大的工作量,因爲作爲回調,實現是放在客戶端上來完成的。

在Client端加入回調實現

因爲我們的aidl接口已經發生了變動,於是需要將新加的ITaskServiceCall.aidl與改變過的ITaskService.aidl文件拷貝到應用程序工程裏。我們再來看一下客戶端實現代碼需要作怎樣的調整:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. import android.os.Bundle;  
  3. import android.os.Handler;  
  4. import android.os.IBinder;  
  5. import android.os.RemoteException;  
  6. import android.app.Activity;  
  7. import android.content.ComponentName;  
  8. import android.content.Context;  
  9. import android.content.Intent;  
  10. import android.content.ServiceConnection;  
  11. import android.view.View;  
  12. import android.view.View.OnClickListener;  
  13. import android.widget.TextView;  
  14.    
  15. import org.lianlab.services.R;  
  16.    
  17. public class MainActivity extends Activity {  
  18.    
  19.    ITaskService mTaskService = null;  
  20.    private TextView mCallbackText;  
  21.    private Handler mHandler = new Handler();  
  22.    
  23.    @Override  
  24.    public void onCreate(Bundle savedInstanceState) {  
  25.        super.onCreate(savedInstanceState);  
  26.    
  27.        try {  
  28.            bindService(newIntent(ITaskService.class.getName()), mTaskConnection,  
  29.                     Context.BIND_AUTO_CREATE);  
  30.        } catch (SecurityException e) {  
  31.            e.printStackTrace();  
  32.        }  
  33.    
  34.        setContentView(R.layout.activity_main);  
  35.    
  36.         ((TextView)findViewById(R.id.textView1)).setOnClickListener(new OnClickListener() {  
  37.            @Override  
  38.            public void onClick(View v) {  
  39.                 if (mTaskService != null) {  
  40.                     try {  
  41.                         int mPid = -1;  
  42.                         mPid =mTaskService.getPid(mCounter); 1  
  43.                         ((TextView)findViewById(R.id.textView1))  
  44.                                .setText("Service pid is " + mPid);  
  45.                     } catch (RemoteException e){  
  46.                         e.printStackTrace();  
  47.                     }  
  48.                 } else {  
  49.                     ((TextView)findViewById(R.id.textView1)).setText("No service connected");  
  50.                 }  
  51.            }  
  52.        });  
  53.    
  54.        mCallbackText = (TextView) findViewById(R.id.callbackView); 2  
  55.        mCallbackText.setText("Clicked 0 times");  
  56.     }  
  57.    
  58.    @Override  
  59.    public void onDestroy() {  
  60.        super.onDestroy();  
  61.        if (mTaskService != null) {  
  62.            unbindService(mTaskConnection);  
  63.        }  
  64.     }  
  65.    
  66.    private ServiceConnection mTaskConnection = new ServiceConnection() {  
  67.        public void onServiceConnected(ComponentName className, IBinder service){  
  68.            mTaskService = ITaskService.Stub.asInterface(service);  
  69.        }  
  70.    
  71.        public void onServiceDisconnected(ComponentName className) {  
  72.            mTaskService = null;  
  73.        }  
  74.     };  
  75.    
  76.    private ITaskServiceCallback.Stub mCounter = newITaskServiceCallback.Stub() {       3  
  77.        public void valueCounted(final int n) {     
  78.            mHandler.post(new Runnable() {     4  
  79.                 public void run() {  
  80.                    mCallbackText.setText("Clicked " + String.valueOf(n) + "  times");  
  81.                 }  
  82.            });  
  83.        }  
  84.     };  
  85.    
  86. }  

新加入的回調接口對象,也沒給我們帶來多大的麻煩,我們最重要是提供一個實例化的ITaskServiceCallback.Stub對象,然後通過getPid()將這一遠程對象的引用發送給Service端。之後,Service處理後的回調請求,則會通過Binder會回到客戶端,調用在Stub對象裏實現的回調方法。所以我們實際增加的工作量,也僅是寫一個ITaskServiceCallback.Stub接口類的實現而已:

  1. 我們需要使用通過getPid(),將ITaskServiceCallback.Stub傳遞給Service。因爲這一對象是用於Service使用的,於是我們必須在使用前先創建,然後再以引用的方式進行傳遞,像我們代碼例子裏的mCounter對象。
  2. 這一部分的改動,只是爲了讓我們檢查效果時更方便,我們通過一個新加的id爲callbackView的textView,來顯示getPid()被調多次的效果。
  3. 這是我們真正所需的改動,通過新建一個ITaskServiceCallback.Stub對象,於是當前進程便有了一個Stub實體,用於實現aidl裏定義的valueCounted()接口方法,對Binder過來這一接口方法的調用請求作響應。
  4. valueCounted()是一個回調方法,從編程模型上我們可以類似的看成是由Service進程所執行的代碼,於是我們需要通過Handler()來處理顯示。當然,在實現上不可能如此神奇,我們可以把一個方法搬運到另一個進程空間裏運行,但valueCounted()既然也不是在主線程環境裏執行,而是通過線程池來響應Binder請求的,於是跟後臺線程的編程方式一樣,我們使用Handler來處理回顯。Handler本身是一種很神奇的實現機制,它可以弱化編程環境裏的有限狀態機的硬性限制,也可以使代碼在拓展上變得更靈活,我們會在後續內容里加以說明。

從上面的代碼可以看出,我們通過aidl創建回調方法好像比我們直接通過aidl寫一個Remote Service還要簡單。事實上,並非回調創建方便,在原則上,我們本只需要一個Stub對象便可以得到我們想要的RPC能力了,只不過出於管理存活週期的需要,才融入到了Service管理框架裏,因爲這種Service使用上的需求才帶來了一些編程上的開銷

AIDL的內部實現

aidl工具的工作原理也很簡單, aidl工具的源代碼在frameworks/base/tools/aidl裏,如果對通過bison來實現編譯器感興趣也可以參考其實現。

而AIDL工具所完成的工作,是將aidl文件轉義成一個通用的Java文件,我們實現的內容,便是拓展自這一Java文件裏的定義。aidl工具生成的結果,一般與aapt工具生成的結果放在同一目錄,在應用程序環境裏,aidl生成的結果是一個在gen/包名/目錄裏與aidl文件前綴名相同的Java文件,我們的例子裏會是gen/org/lianlab/services/ITaskService.java,我們可以看一下這個生成文件的內容:

[java] view plaincopy
  1. package org.lianlab.services;  
  2. public interface ITaskService extends android.os.IInterface { 1  
  3.     public static abstract class Stub extendsandroid.os.Binderimplements  
  4.            org.lianlab.services.ITaskService { 2  
  5.        private static final java.lang.String DESCRIPTOR = "org.lianlab.services.ITaskService";   3  
  6.    
  7.        public Stub() {  
  8.            this.attachInterface(this,DESCRIPTOR);   4  
  9.        }  
  10.    
  11.        public static org.lianlab.services.ITaskService asInterface(android.os.IBinderobj) {  5  
  12.            if ((obj == null)) {  
  13.                 return null;  
  14.            }  
  15.            android.os.IInterface iin = (android.os.IInterface)obj.queryLocalInterface(DESCRIPTOR);  
  16.            if (((iin != null) && (iin instanceof org.lianlab.services.ITaskService))) {  
  17.                 return((org.lianlab.services.ITaskService) iin);  
  18.            }  
  19.            return new org.lianlab.services.ITaskService.Stub.Proxy(obj);  6  
  20.        }  
  21.    
  22.        public android.os.IBinder asBinder() {  
  23.            return this;  
  24.        }  
  25.    
  26.        @Override  
  27.        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply,  
  28.                 int flags)throwsandroid.os.RemoteException {  7  
  29.            switch (code) {  
  30.                 case INTERFACE_TRANSACTION: {  8  
  31.                     reply.writeString(DESCRIPTOR);  
  32.                     return true;  
  33.                 }  
  34.                 case TRANSACTION_getPid: {    9  
  35.                     data.enforceInterface(DESCRIPTOR);  
  36.                     int _result =this.getPid();  
  37.                     reply.writeNoException();  
  38.                     reply.writeInt(_result);  
  39.                     return true;  
  40.                 }  
  41.            }  
  42.            return super.onTransact(code, data, reply, flags);   10  
  43.        }  
  44.    
  45.        private static class Proxy implements org.lianlab.services.ITaskService { 11  
  46.            private android.os.IBindermRemote;  12  
  47.    
  48.            Proxy(android.os.IBinder remote) {  
  49.                mRemote = remote;  
  50.            }  
  51.    
  52.            public android.os.IBinder asBinder() {  
  53.                 return mRemote;  
  54.            }  
  55.    
  56.            public java.lang.StringgetInterfaceDescriptor() {  
  57.                 return DESCRIPTOR;  
  58.            }  
  59.    
  60.            public int getPid() throws android.os.RemoteException { 13  
  61.                 android.os.Parcel _data =android.os.Parcel.obtain();  
  62.                 android.os.Parcel _reply =android.os.Parcel.obtain();  
  63.                 int _result;  
  64.                 try {  
  65.                     _data.writeInterfaceToken(DESCRIPTOR);  
  66.                     mRemote.transact(Stub.TRANSACTION_getPid,_data, _reply, 0);  
  67.                     _reply.readException();  
  68.                     _result = _reply.readInt();  
  69.                 } finally {  
  70.                     _reply.recycle();  
  71.                     _data.recycle();  
  72.                 }  
  73.                 return _result;  
  74.            }  
  75.        }  
  76.    
  77.        static final int TRANSACTION_getPid = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);    14  
  78.     }  
  79.    
  80.     public int getPid() throwsandroid.os.RemoteException;   
  81. }  

       上述生成的ITaskService.java文件,在類圖上大致由如下的關係來構成:


IInterface接口類在Binder框架裏是兩邊訪問的基礎,只通過asBinder訪問一個IBinder對象。在Proxy模式裏核心接口類Interface部分的功能,實際上則由拓展IInterface的自定義的ITaskService來完成,通過同一接口,客戶端會得到Proxy對象,而Service會得到Stub對象。在Android環境裏,Proxy只是Stub類的一部分,這是Proxy在實現上的一種變體,從而強調Stub作爲實現上的重要性,Proxy需要依賴於其對應的Stub接口的存在。我們可以詳細分析一下,這些自動生成的代碼的含義:

  1. 定義一個ITaskService接口類,繼承自同樣是接口類的IInterface。IInterface接口類的作用很簡單,就是能夠通過asBinder()方法返回一個IBinder接口對象。IBinder本質上也是接口類,不過比IInterface要複雜,這一接口用來細分出一些Binder通信上的接口,從而基於同一接口,在發送端實現發送代碼transact(),在接收端實現接收代碼onTransact()。像我們前面看到的所有Binder對象,其實都是繼承自這一接口,於是使用Binder對象天生是跨進程對象,所以有繼承自Binder的類,被創建後都會是具備跨進程能力的遠程對象。從這裏的實現,我們也可以看到,IBinder與Binder 的接口與實現抽離,可以使用我們可以靈活地使用統一的IBinder接口訪問到不同進程空間裏的Binder對象。ITaskService接口類,實際上很簡單,就是定義一個抽象類Stub,以及aidl裏定義的接口方法。
  2. 定義一個抽象類Stub,同時繼承Binder類,並實現ITaskService接口。繼承Binder,使Stub對象將成爲跨進程的遠程對象,而實現ITaskService接口,則使Binder IPC通信裏發生的ITaskService相關的操作,都將轉發到Stub對象的onTransact()方法裏處理。作爲抽象類的Stub,於是它不可能單獨被實例化,必須通過實現抽象類裏缺失的方法來得到一個實質的類,因爲我們的ITaskService接口類只需要getPid()實現,於是我們的Stub所缺失的也只有這一方法。
  3. Stub裏會包含一個靜態、final類型的字符串DESCRIPTOR,這一字符串用於Binder通信時的標識。DESCRIPTOR在aidl翻譯時會使用aidl接口類的名字,所以這一字符串是不會重複的。
  4. 在Stub對象的初始化方法裏,會調用attachInterface()將DESCRIPTOR註冊到Binder對象裏,於是當一個完整實現的Stub對象被創建時,以它的接口類名爲標識的信息就會註冊到Binder通信裏,從而Binder IPC裏以 DESCRIPTOR爲目標的消息就會傳到這一Stub實現裏。
  5. asInterface()是提供給客戶端來調用的方法。在基於aidl的編程裏,客戶端通過onServiceConnected()會取回一個IBinder引用,而我們通過Stub對象的asInterface()方法,就可以取回一個客戶端可用的Proxy實例。這是Android的技巧所在,通過onServiceConnected()返回的,只是一個IBinder引用,在進程空間不會存在具體的對象,因爲通過一個接口類ITaskService是無法得到具體的對象的,於是通過這個asInterface()的所謂類型轉換,實質卻是通過這次調用創建了一個Proxy對象。於是客戶端有了Proxy對象,向Service端的Stub端對象發送命令,這樣RPC交互便建立起來了。
  6. 在建立起與Stub對象通信的過程前,本地IInterface爲空引用,於是創建一個Stub.Proxy對象。在後面的Stub.Proxy實現部分,我們可以看到在Stub.Proxy對象被創建後,調用ITaskService的方法,實際上會通過不同的接口方法實現,發送Binder命令到遠端。
  7. 對於Stub端代碼的實現,我們前面也分析過,就是通過把IPC消息取出來解析,找到需要執行哪個方法來響應具體的調用請求。在Android環境裏,這些操作統一由onTransact()方法來完成。在Java裏對於異常的捕捉與處理很重要,而我們所有的遠程方法調用,實際都會是在onTransact()作用域裏來完成,於是我們在這裏拋出RemoteException,遠程調用上任何的異常信息都將在這時會集中到一起,通過Binder拋到客戶端處理。
  8. INTERFACE_TRANSACTION,值爲1,這是Binder跨進程交互的最基本通信接口,用於查詢這一接口是否存活。這一TRANSACTION在處理上就比較簡單,只是將自己的DESCRIPTOR寫入Binder返回給客戶端。
  9. TRANSACTION_getPid,對於aidl裏定義的每個方法,最終都會將當前Binder通信的命令+1,從而可以拓展出新的命令請求,於是這個值在客戶端環境和Service端環境都是統一的。在通過Binder接收這樣的Binder命令之後,就會調用具體的Stub裏實現的方法,比如我們實現的getPid(),然後再將返回值通過Binder傳回給客戶端。
  10. 這是IoC設計模式的又一次應用。在Android源代碼裏,我們經常看到這樣成功實施的設計模式反覆被使用,簡直讓人懷疑這整套系統都是由一個人設計實現。在Stub類裏,實際上繼承了Binder類然後又覆蓋掉了onTransact()方法,而在覆蓋掉的onTransact()方法裏又回調到父類Binder的onTransact()方法,實際上就拓展了Binder的執行能力,但又保持了原有的接口方法。
  11. 定義客戶端調用所需要的Proxy類。Proxy與Stub一樣,都繼承自ITaskService,於是也會跟Stub通過ITaskService接口類的定義來共享接口方法。
  12. 在這裏,我們就可以看到接口與實現分享的好處了,我們的代碼通過IBinder接口來訪問某一對象,於是在Service實現裏得到的Stub,而在客戶端得到的是Stub.Proxy對象。這樣的特點在繼承於Binder的遠程對象裏也是如此,Binder對象同時存在客戶端的請求代碼與Service端的響應代碼,而在客戶端與Service端都使用IBinder接口來訪問Binder時,就可以得到不同的實現。
  13. 這是我們Proxy部分的代碼實現,比如我們例子裏定義的是getPid()的接口方法,在這裏就會有一個getPid的Proxy接口方法實現。在這一方法裏,會通過Binder通信,將TRANSACTION_getPid的命令發送到標識爲DESCRIPTOR的響應請求的部分。對應於我們前面描述的onTransact()實現,我們就會知道,後續動作就是觸發遠程的getPid()被執行到。消息發送會是阻塞操作,當代碼得以繼續時,遠程代碼肯定已經執行完成,這時就會通過Binder將遠程發過來的返回結果讀出來,返回給調用getPid()的部分。當然,遠程的onTransact()實際上還有可能通過Binder將RemoteException拋出來,於是我們這裏也將讀取這些Exception,再轉發給上層異常捕捉代碼。
  14. 所有的Binder所能收發的命令,都是按INTERFACE_TRANSACTION + n的方法加入系統裏的,這樣拓展起來很靈活。由於都是基於同一ITaskService.java來提供這些命令定義,於是客戶端與Service端是共享這些命令定義的。
所有的上述的代碼,就是只爲了實現一個接口方法而用,要是有更多的接口方法的定義,則這些自動生成的代碼將會很長。代碼複雜,又是需要跨進程交互的處理,在編程上很容易出錯,而aidl工具的使用,大大減小了我們編程的工作量,也減小了出錯的概率。

總而言之,這些代碼,就將客戶端與Service端的代碼,分別通過Stub與Stub.Proxy兩個類來創建,通過這兩個對象的引入,無論是調用RPC,還是提供RPC實現,都變得只是拓展一下原有的基本實現即可。這時就基本上達到我們前面描述過的基於Binder進行RPC通信的需求:

 

而在遠程交互的實現上,我們基於AIDL的Remote Service,就會以如下的形式進行交互:


在編譯階段,idl文件裏定義的接口會通過aidl工具翻譯得到一個ITaskService.java文件,保存到gen供應用程序引用。這一ITaskService.java文件裏會具體實現發送請求的Proxy接口類與提供實現並將執行結果返回的Stub接口類。在發生aidl調用時:

  1. 客戶端調用bindService(),以某個Intent作爲參數。這一Intent裏會通過Action將請求發送到Service實現,於是觸發Service實現的onBind()回調。
  2. Service裏實現onBind()會返回一個IBinder引用,通過觸發客戶端的onServiceConnected()回調方法,傳遞給客戶端。實際上,在建立Binder通信之前的這些跨進程通信都是由ActivityManager來完成的,並不是直接跟Binder打交道,而是由Intent來觸發。
  3. 取回IBinder引用之後,通過ITaskService.Stub.asInterface()方法,這一Binder會被轉換成Proxy對象。當然,我們從底層已經看到,這個所謂的轉換,實質上是創建了一個ITaskService.Stub.Proxy對象。
  4. 當我們需要從客戶端進程訪問到服務端進程時,實際上,都是通過已經取得的Stub.Proxy對象裏的getPid()方法,將一個執行的請求通過Binder發送給Service裏實現的Stub對象。
  5. Stub對象通過自己的onTransact()方法,讀取發送到自己進程的Binder命令,根據不同Binder命令執行不同的遠程方法,然後將執行完的結果通過Binder返回給客戶端。

當然,此時我們得到了基本的跨進程調用方法的能力,這跟我們普通的編程模型一致了,像我們的一般的編程,都是從一個main()函數開始,調用不同的代碼邏輯,最終得到一個大的複雜的可執行程序。但這並非全部,在現實的編程環境裏,我們還需要實現一些回調式的編程,主要用於一些直接調用不太合適的情境。比如我們需要在後臺做某些比較耗時的操作,像下載、登錄等,如果直接調用再取返回值,此時,我們得到的結果就很悲慘,會需要等待函數執行完成之後才能繼續執行,需要等待很長時間。在Android的單線程編程模型裏,這種情況會更嚴重,我們Activity運行在主線程裏,如果通過aidl調用到一個耗時操作,就會阻塞到主線程的執行,從而產生ANR錯誤。

這種方式實現的,只是單向調用,雖然我們也可以在發起調用的部分取得返回值,但只是一種單向的函數式調用。如果我們需要實現雙向調用怎麼做呢?我們不光會調用到遠程代碼裏,也可以提供一種回調接口,讓被調用方回調回來,就像是C語言裏的回調函數,這樣,遠程調用的世界就完整了,見後續內容。


RPC,以及爲什麼需要這麼複雜的處理

我們可以先來了解一下RPC的實現。

跨進程訪問,實際上並非Android環境才需要,這是所有跨進程軟件設計裏的必須項。這種交互性的跨進程需求,跟我們傳統的C/S(客戶端/服務器)構架類似,客戶端使用IPC訪問服務,而服務器端則實現具體的代碼邏輯,通過IPC提供服務。如下所示

進程1提供客戶端功能,而進程2提供服務器功能,在進程1裏調用RPCFunc(1,2),實際上會觸發到進程2裏的RPCFunc1Impl1(1,2)的執行。

  1. 需要通過IPC機制在底層把這樣的訪問實現出來,這樣在客戶端進程空間裏可以找到RPCFunc1Stub()的定義,用於將函數調用解析爲基於IPC的請求消息
  2. 在RPCFunc1Stub()實現裏,會將訪問請求轉義成具體的進程間通信的消息,再發送出去
  3. 服務器端則會RPCFunc1Skel()來監聽所有的請求,然後再具體調用請求轉發到自己實現的RPCFunc1Impl()
  4. 當RPCFunc1Impl()執行完成之後,反將結果返回到RPCFuncSkel()
  5. 返回的值則會再次經由進程間通信,再傳回給客戶端
  6. 然後在客戶端RPCFunc1()的return語言裏返回

這樣,從進程1的代碼上來看,好像是完成了一次從RPCFunc1()到RPCFunc1Impl()的遠程過程調用(RPC),但在內容實現上,則是經歷了1-6這樣6個步驟的串行操作。之說以說是串行,因爲這6個步驟會順序進行,進程1的執行RPCFunc1()這一函數時,直到第6步執行完成之前,都會被阻塞住。

通過通過串行實現後的這種特殊C/S框架,因爲跟我們傳遞的函數調用類似,只是提供了跨進程的函數調用,於是根據這樣的行爲特徵,我們一般會叫它們爲遠程過程調用(Remote Procedure Call,簡稱RPC)。支撐起RPC環境的是IPC通信機制,我們也知道套接字(Socket)也是IPC機制一種,而TCP/IP是Socket機制的一部分,於是很自然的,RPC也天生具備跨網絡的能力,在實際的部署裏,RPC一般會是網絡透明的,通過RPC來進行訪問的那端,並不會知道具體實現RPC的會是本地資源或是網絡上的資源。RPC是實現複雜功能的基礎,特別是一些分佈式系統設計裏,比如我們Linux環境裏的網絡文件系統NFS、AFS等,都是基於RPC,還有更高級一點,像我們的Corba環境、J2EE環境和WebService,都會使用RPC的變種。

擁有了RPC通信能力之後,我們在編程上的侷限性便大爲減小,我們的代碼可以很靈活地通過多進程的方式進行更安全的重構,而且還可以進行伸縮度非常良好的部署。這點對於Android來說,尤爲重要,因爲我們的Android系統就是構建在基於多進程的“沙盒”模型之上的。在Android環境裏,不存在基於網絡來進行RPC的需求,而是使用高性能的面向對象式的Binder,於是,我們的RPC,需要通過RPC來構建。於是,簡單的RPC通信流程,在Android系統裏,則可以通過IBinder對象引用來完成,得到如下的實現邏輯:


我們會通過IBinder,來實現從一個進程來訪問另一個進程的對象,得到遠程對象的引用之後,雖然在進程1裏我們像是真正通過這一IBinder來訪問遠程對象的某些方法,比如doXXX()方法,但實際上後續的執行邏輯則會被轉到Binder IPC來發送訪問進程,進程1在進入doXXX()方法之後,就會進入IBinder是否有返回的檢測循環。當然此時由於IBinder設計上的精巧性,此時進程實際上會休眠到/dev/binder設備的休眠隊列裏。而提供RPC的進程2則相反,它啓動後會一直在IPC請求上進行循環監聽,當有IPC請求過來之後,則會將doXXX()的訪問請求解析出來,訪問這一方法,在訪問完成之後,再將調用doXXX()的結果通過IBinder返回給發出調用請求的進程1。這時,會喚醒進程1繼續往下執行,從IPC上取回調用的返回值,然後再執行doXXX()之後的代碼。

通過這種RPC機制,在Android系統裏,就可以更靈活地來設計交互過程,更方便地在多進程環境裏進行低耦合化設計。在RPC交互的Server端的實現,可以靈活地根據自己的實現或是調用上的需求,開放出來一部分的自己實現的接口,從而給多個Client端提供服務。

由於Binder能夠支持RPC,則基於代碼有可能會變得異常複雜,於是,在實際的編程過程裏,我們也還需要其他的輔助手段。比如,在實際的實現裏,我們都會存在大量的RPC訪問:

在這種大量的RPC實現裏,會有大量地處理RPC調用的重複代碼,比如RPC的發送部分,Server端實現的IPC解析與分發部分。這些重複代碼是沒有意義的,而且在實際過程裏,這種重複代碼也將會是錯誤的源頭。想像一下,如果上圖所描述的RPC有100個,此時,我們將需要實現一個多大的switch()跳轉。還有一個設計上的問題,當然我們使用固化的switch()來處理這種大量分支跳轉,則我們的代碼在設計上會被固化,我們不能靈活地重構我們的代碼。


於是,我們在實現Service時,我們先會使用Proxy模式來進行重構。標準的Proxy模式構成如下:


對於同一Subject接口類,會被拆分成Proxy與具體實現的SubjectImpl類,方法的實現在SubjectImpl類裏完成,而Proxy類所實現的則是將某些方法調用轉發到SubjectImpl類。當客戶端通過統一的Subject對象進行訪問時,實際上是通過Proxy類來完成這轉發。這樣,接口訪問與接口的實現則會隔離開,只通過基本的接口類Subject進行交互,從而降低了設計上的耦合性。


在Android的跨進程調用裏使用Proxy模式,則得到如下的示意圖:


Android裏使用的Proxy模式,也是通過一個接口類IInterface派出來具體一個Proxy類和一個Stub類,由Proxy來提供訪問接口的解析能力,而具體實現由Stub類來提供。於是,我們可以不再使用底層的IBinder來訪問遠程對象Service,而是通過一個抽象的接口類IInterface來進行訪問。對應於每個IInterface抽象類,會派生出兩個不同類,BpXXX和Stub,BpXXX用來提供Proxy功能,Stub類來提供具體實現。BpXXX,它的命名是Binder + Proxy + XXX的簡寫,XXX是指代具體的Service名字,比如Battery。通過這種模式重構成出來的實現就會將我們的接口的訪問與實現完成的抽離開來,我們的應用程序進程裏會各有一個BpXXX對象,而服務器裏會有一個Stub對象,BpXXX與Stub通過IInterface來統一所能進行交互的接口方法,在應用程序裏通過一個BpXXX,則可以訪問到Service裏實現的Stub對象的方法。於是,我們的服務器則只需要專注於方法的提供,通過繼承Stub對象,然後實現IInterface接口類裏定義的方法。

我們可以注意到,實際上,IInterface<XXX>接口、BpXXX與Stub,這三者在我們圖中是被一個Framework的範圍之內,當然最好這部分通過Proxy設計模式重構過的訪問模式之後,重複性的代碼都可以被通用化的代碼實現,以減小工作。但這不現實,我們無法預測出某個被RPC化的對象會提供哪些方法,我們在系統裏唯一可能比較明確的部分是IInterface這樣的接口。這時,如果我們使用某種特殊工具,將IInterface對於接口訪問方法(客戶端與服務器所統一出來的遠程調用方法)使用某種中間語言描述出來,這時,我們就可以得到我們想要減小重複代碼的目的了,這樣的工具就是IDL,在Android裏被進一步簡化成了Android版的 IDL,於是被稱爲AIDL。


IDL,全稱是接口定義語言(Interface Definition Language),也是通過制定UML規範的OMG組織提出的交互的接口規範。IDL,是把RPC調用的實現,通過一種抽象的接口語言IDL定義出來,從面可以實現跨平臺、跨語言、跨網絡的調用環境。既然這種IDL語言只是一種規範,於是針對不同軟件開發框架會提供不同的IDL解析工具,就好像HTML是一種規範,而瀏覽器與HTTP服務器可以有各種不同實現一樣。幾乎每種開發環境,都會構建出自己的一套IDL工具,從而實現不一樣的交互性開發需求。比如我們最常用的可能會是Windows上的COM/DCOM接口,專用於Windows平臺裏的跨進程跨網絡環境的開發;而像Mozilla或是OpenOffice這樣軟件環境裏的IDL支持,IDL存在的意義更多地則是着眼於多語言支持,可以實現更加方便的插件開發,雖然也提供跨進程支持,但不是重點,更加不支持網絡。最強大的IDL的應用環境CORBA(通用對象請求代理框架,Common Object Request Broker Architecture)則着眼於完整的跨平臺計算環境的提供,從而支持多語言多平臺跨網絡,可以將異構型計算環境合併成統一計算環境,Java EE也會使用CORBA作爲其網絡交互的底層機制。

這些不同IDL工具所能起到的作用倒是很類似的,就是將標準的IDL語言,轉化成自己平臺開發語言的基類實現,然後再由調用端和實現端分別實現具體的接口。比如我們前面舉的RPC的例子,如果使用IDL的C語言綁定,則會像是下面的這個樣子:

IDL文件裏定義接口方法,xxx.idl文件裏會定義接口RPCFunc1。而IDL通過特定IDL工具翻譯之後,會生成針對客戶端的xxxStub.h,和針對服務器端的xxxSkel.h,然後我們可以通過這兩個頭文件,會定義其具體所要求的客戶端的RPCFunc1Stub()的實現和服務器端的RPCFunc1Skel()實現。實現完成的結果就是我們會得一個基於IPC的跨進程調用RPCFunc1(arg1,arg2)。當然,如果是有多語言支持,這時我們還會生成不同語言支持下的版本,比如在面向對象的語言環境裏會生成xxxStub和xxxSkel基類,我們可以繼承基類,然後再改寫這特定的方法。

如果是在跨語言支持、跨網絡、跨平臺的環境裏,IDL生成的客戶端與服務器端實現還是會由於複雜的應用程序情境而需要我們自已在具體實現裏進行定製,比如處理面向對象與面向過程之間、網絡與單機環境之間、操作系統之間、大小端等諸多方面的差異性。而我們的Android則要簡單得多,我們對於IDL的需求僅只是解決跨進程調用時的問題,只需要支持Java語言的綁定,只需要在Android環境裏運行,只需要單機環境支持。於是,IDL本身,可以被大幅度簡化,於是便有AIDL。

AIDL就是Android裏的IDL,只提供Java環境支持,只針對Android環境,只應用於單機環境。引入了AIDL之後的Proxy模型,則幾乎可以開始進行“傻瓜”式的RPC編程了。AIDL的作用如下圖所示:

針對於前面看到的Interface、Proxy、Stub三個類的代碼重複問題,在AIDL裏得到了很好的解決方案,有了AIDL之後,這三個類的定義是由AIDL工具轉義自動生成的代碼。我們在系統裏會有.aidl文件,這是一種簡化過的面向Java的IDL文件,在這一文件裏定義調用方與被調用方所統一使用的接口方法。AIDL文件會通過AIDL編譯工具編譯成Interface、Proxy、Stub三個基類的定義,當然這三個類在Binder環境裏會是,IInterface<XXX>,BpXXX,與XXX.Stub,這些是自動生成的,不需要修改。應用程序這端會通過Interface定義,直接訪問遠端的Stub,但實際上底層會通過BpXXX這個Proxy類來完成轉發。而我們實現的部分,只需要在Service類裏繼承並實現Stub類的拓展接口即可。於是,我們便可以很方便地基於Binder得到了RPC的多進程交互的能力。

通過AIDL來編跨進程的Remote Service,是Android裏提供的一種功能強大,但編寫簡單的一種編程模型。我們需要通過Java編程,透過Binder IPC來給系統裏的其他部分,或是應用程序共享某些功能接口時,必須通過AIDL來進行編程。所以AIDL不光是對於應用程序的編程很重要,對於Android系統層開發也很重要,而AIDL在編程上並沒有帶來很大的開銷,並不是特別複雜。我們先來看一下Android裏的AIDL編程。 

AIDL的語法

AIDL的語法其實很簡單,基本上我們可以把它看成Java語言裏一種類似於C頭文件的格式,它只包括方法的定義,但不包含實現。它大部分是定義一個或者多個的接口方法,通過對這些接口方法的抽象定義,會定義方法的參數列表與返回值。這種定義方法的AIDL是AIDL解析時的入口,但爲了支持數據類型的拓展,它還支持通過Parcelable接口類來拓展數據類型的支持。這是因爲AIDL在語法上僅支持Java的基本數據類型和一些Android環境裏的基本類,這樣可以使用AIDL整個環境的實現和自動生成的代碼都可以變得簡單,AIDL不直接支持的數據類型,則需要通過引用其他的AIDL定義來導入進來。

AIDL支持數據類型:

  •  Java的基本數據類型(元數據類型,Primitive JavaProgramming Language Types),像int, boolean等。
  •  以下幾種基本類:
    •    String
    •    List
    •    Map
    •   CharSequences
  •    通過AIDL導入其他AIDL文件裏定義的內容,一般用於導入其他的接口類
  •    通過AIDL導入的其他Parcelable的接口類,用於導入AIDL所不能支持的數據類型
  •    AIDL只能用於定義方法,不能用於定義類結構,雖然也會導入Parcelable接口定義,但Parcelable只是描述存在某個類,並不會描述類的結構
  •    AIDL在參數上會使用in, out, inout三種描述符,主要是用於通過Binder傳遞時指定單向入,單向出,和雙向傳遞。我們可以從後面的Parcel看到,在跨進程間傳遞數據還是有開銷的,通過這種指定方式可以有更高效率。比如in、out會指定只在收發兩端進行一次複製,inout則需要進行雙向複製,從客戶端複製到服務端,然後在調用完成後再從服務端發送回客戶端。我們可以根據參數用途進行具體指定,如果不加指定,參數默認爲in類型。

有了這樣數據類型支持之後,實際上AIDL,可以應用於幾乎任何情境。我們定義一個AIDL文件時,可以通過在Eclipse的應用程序工程里加入.aidl文件,將這一文件放在src/包名/的目錄下,這樣ADT工具會自動使用AIDL工具來編譯這一文件,也可以手工添加這樣的文件,然後使用Android.mk文件指定AIDL工作來進行編譯。最後AIDL都會將編譯結果放在gen目錄裏(跟R.java的保存位置一樣)。

定義一個AIDL文件很簡單,基本上可以認爲是把Java定義裏的屬性與實現部分去掉,把得到的文件命名爲.aidl即可。出於命名的規範性,一般我們把要需要拋出接口的類定義之前加大寫的I(Interface的首字母),比如我們希望聲明一個遠程接口類TaskService,我們一般會定義一個叫ITaskService.aidl的文件。當然,既然aidl作用於Java,於是我們也需要指定包名,以限定該接口類的域名空間,這時跟Java語言一致,使用package加包名。

packageorg.lianlab.service

然後在這一行之後,通過一個interface定義我們需要拋出來的接口類,這一般會與我們.aidl文件名是一致的,比如我們前面的ITaskService.aidl,則我們會使用interface ITaskService{},然後再在ITaskService的作用域內,定義我們需要使用的方法。最後我們得到的.aidl文件,大概會是這個樣子:

[java] view plaincopy
  1. packageorg.lianlab.service;  
  2. interfaceITaskService {  
  3.     int getPid ( );  
  4. }  

是不是很簡單?其實對應於Java實現,因爲aidl文件裏只是定義接口類裏的方法,於是不需要額外的引用,所以內容會很簡單。因爲我們現在需要的這兩個遠程接口方法,都只使用最基本的數據類型,如果是需要使用aidl支持的數據類型以外的類,則我們還會需要引用其他的aidl文件,將這些新的數據定義導入我們當前的aidl定義,這時我們會使用import語句來導入這樣的aidl定義。

所以綜合一下,AIDL基本語法便是由package、import、interface這樣格式構成,然後再在interface裏定義所需要使用的方法。使用import來引用各個aidl文件裏定義的接口類,則避免了接口類的重複定義,而通過import引用Parcelable接口類,則使AIDL能夠很靈活方便地傳遞複雜數據結構。唯一需要注意的是,AIDL的解析工具在實現上很簡單,並沒有複雜的容錯性檢查,在編寫AIDL時我們需要注意格式問題,比如intgetPid(void);則是不對的格式。

稍後來看到,我們怎麼樣針對這麼簡單幾行的AIDL文件編寫一個Remote Service,提供這個特殊的getPid()遠程調用。


轉自:http://blog.csdn.net/21cnbao/article/details/8086487

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章