基礎篇-Binder機制和AIDL使用介紹

分類:IT技術 時間:2016-10-17
簡介:         我們知道,android系統是基於linux內核的,而Linux內核繼承和兼容了豐富的Unix系統進程間通信(IPC)機制。例如管道(Pipe),報文隊列(message)、共享內存(Share Memory)和信號量(Semaphore)等等。但是Android系統沒有采用上述提到的各種進程間通信機制,而是采用Binder機制。在Android系統的Binder機制中,由一系列系統組件組成,分別是Client、Server、Service Manager和Binder驅動程序,其中Client、Server和Service Manager運行在用戶空間,Binder驅動程序運行內核空間。Binder就是一種把這四個組件粘合在一起的粘結劑了,其中,核心組件便是Binder驅動程序了,Service Manager提供了輔助管理的功能,Client和Server正是在Binder驅動和Service Manager提供的基礎設施上,進行Client-Server之間的通信。Service Manager和Binder驅動已經在Android平臺中實現好,開發者只要按照規範實現自己的Client和Server組件就可以了。說起來簡單,做起難,對初學者來說,Android系統的Binder機制是最難理解的了,而Binder機制無論從系統開發還是應用開發的角度來看,都是Android系統中最重要的組成,因此,還是有必要了解一下,樓主沒有閱讀過源碼,只能淺談一下自己的認識。


一.Binder機制關系圖:



        1. Client、Server和Service Manager實現在用戶空間中,Binder驅動程序實現在內核空間中         2. Binder驅動程序和Service Manager在Android平臺中已經實現,開發者只需要在用戶空間實現自己的Client和Server         3. Binder驅動程序提供設備文件/dev/binder與用戶空間交互,Client、Server和Service Manager通過open和ioctl文件操作函數與Binder驅動程序進行通信         4. Client和Server之間的進程間通信通過Binder驅動程序間接實現         5. Service Manager是一個守護進程,用來管理Server,並向Client提供查詢Server接口的能力  ServiceManager,這是Android OS的整個服務的管理程序,任何service在被使用之前,均要向SM(Service Manager)註冊,同時客戶端需要訪問某個service時,應該首先向SM查詢是否存在該服務。如果SM存在這個service,那麽會將該service的handle(每個service的唯一標識符)返回給client,client初始化binder代理接口(打開/dev/binder設備;在內存中為binder映射128K字節空間),對對應service直接調用,代理接口中定義的方法與與server中定義的方法是一一對應的,代理接口的方法會將client傳遞的參數打包成為Parcel對象(Parcel是binder IPC中的最基本的通信單元,它存儲Client-Server間函數調用的參數.但是Parcel只能存儲基本的數據類型,如果是復雜的數據類型的話,在存儲時,需要將其拆分為基本的數據類型來存儲),發送給內核中的binder driver;其中維護了一個死循環,不停地去讀內核中binder driver,查看是否有可讀的內容;即是否有對service的操作要求, 如果有,則調用svcmgr_handler回調來處理請求的操作,整個的調用過程是一個同步過程,在server處理的時候,client會block住。
如果對於Binder機制的原理和源碼實現感興趣的,可以去看看老羅的bolg介紹,這裏暫時介紹下Java層實現的方式吧,主要以調用遠程服務的過程來解析。


二.遠程服務


1.遠程服務介紹

由於每個應用程序都運行在自己的進程空間,應用程序之間不能共享內存,訪問其他應用的服務,關鍵就在於跨進程通信。 通過代碼來實現這個數據傳輸過程是冗長乏味的,Android提供了AIDL工具來處理這項工作,Binder是Android系統進程間通信(IPC)方式之一AIDL(Android interface Definition Language) 的 IPC機制是面向對象的,輕量級的。通過AIDL定義的接口可以實現服務器端與客戶端的IPC通信。它是使用代理類在客戶端和實現端傳遞數據

2.遠程服務使用場景介紹

(1)如果不需要進行不同應用程序間的並發通信(IPC)通過實現一個Binder對象來創建接口就行,比如你和這個遠程服務是同一個進程中 (2)你想進行IPC,簡單Message消息傳遞,不涉及多線程處理,則使用Messenger對象來創建接口 (3)AIDL:只有你允許客戶端從不同的應用程序為了進程間的通信而去訪問你的service,以及想在你的service中多線程處理時使用這種方式實現。

3.這裏就先簡單介紹下Messenger的方式


Messenger簡介 看一下Messenger的源碼實現,就知道了,其就是依賴了一個默認設計的Imessenger.aidl文件,繼承了IMessenger.Stub類,實現了send方法,send方法中參數會通過客戶端傳遞過來,最終發送給handler進行處理。 而Messenger是和Handler以及IBinder綁定在一起的,因此Messenger的構造函數有兩種: a. 本地Messenger是傳入一個Hanlder,根據傳入的Handler創建Messenger,且該Messenger指向該Handler,當我們向Messenger發送信息的時候,Handler會受到信息並處理消息,該構造函數往往是在某個類中構建該類自身的Messenger,比如在MyService中用ServiceHandler的實例初始化了自身的serviceMessenger以及在客戶端中用ClientHandler的實例初始化了其自身的clientMessenger。這種Messenger可以看做是本地的Messenger。創建完的Messenger可以通過getBinder()方法得到對應的IBinder類型的實例,簡單來說,就是包含本地應用的Handler。 b. 遠程messenger,是傳入一個IBinder,根據傳入的IBinder實例創建一個遠程的Messenger。這種構造函數往往是在客戶端中,通過得到ServiceonBind方法返回的IBinder,然後基於此IBinder初始化一個遠程的Messenger。該Messenger指向的是Service,而不是客戶端,所以該Messenger就是一種遠程的Messenger。比如客戶端中的serviceMessenger就是一種遠程的Messenger,指向的是MyService,簡單來說就是包含遠程應用的Handler

通信步驟

(1). 這個Service中需要實現一個Hanlder,用以處理從所有客戶端收到的消息,阻塞處理,一般不耗時
private class ServiceHandler extends Handler {
        @Override
        public void handleMessage(Message msg) {
            if(msg.what == RECEIVE_MESSAGE){
                Bundle data = http://blog.csdn.net/gjr9596/article/details/msg.getData();
                if(data != null){
                    String str = data.getString("msg");
                    Log.i("DemoLog", "收到客戶端如下信息: " + str);
                }
                //通過Message的replyTo獲取到客戶端自身的Messenger,
                //Service可以通過它向客戶端發送消息
                clientMessenger = msg.replyTo;
                if(clientMessenger != null){
                    Log.i("DemoLog", “服務端向客戶端回信");
                    Message msgToClient = Message.obtain();
                    msgToClient.what = SEND_MESSAGE;
                    //可以通過Bundle發送跨進程的信息
                    Bundle bundle = new Bundle();
                    bundle.putString("msg", "你好,客戶端,我是服務端”);
                    msgToClient.setData(bundle);
                    try{
                        clientMessenger.send(msgToClient);
                    }catch (RemoteException e){
                        e.printStackTrace();
                        Log.e("DemoLog", "thisservice向客戶端發送信息失敗: " + e.getMessage());
                    }
                }
            }
        }
    }


(2).用該Handler創建一個Messenger對象,Messenger對象內部會引用該Handler對象
Messenger serviceMessenger =new Messenger(newServiceHandler())


(3). 用創建好的Messenger對象獲得一個IBinder實例,並且將該IBinder通過ServiceonBind方法返回給各個客戶端
 publicIBinder onBind(Intent intent) {
//獲取Service自身Messenger所對應的IBinder,並將其發送共享給所有客戶端
returnserviceMessenger.getBinder();
}


(4).客戶端通過bindService(intent,serviceConnection,autocreate),連接成功後獲取IBinder對象實例化一個Messenger對象,該Messenger內部指向指向外部Service中的Handler。客戶端通過該Messenger對象就可以向Service中的Hanlder發送消息。
private ServiceConnection conn = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            serviceMessenger = new Messenger(binder);
            Message msg = Message.obtain();
            msg.what = SEND_MESSAGE;
            Bundle data = http://blog.csdn.net/gjr9596/article/details/new Bundle();
            data.putString("msg", "你好,我是客戶端");
            msg.setData(data);
            //需要將Message的replyTo設置為客戶端的clientMessenger,
            //以便Service可以通過它向客戶端發送消息
            msg.replyTo = clientMessenger;
            try {
                serviceMessenger.send(msg);
            } catch (RemoteException e) {
                e.printStackTrace();
                Log.i("DemoLog", "客戶端向service發送消息失敗: " + e.getMessage());
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            //客戶端與Service失去連接
            serviceMessenger = null;        }
    };


(5). Service中的Hanlder收到消息後,在Handler中的handleMessage方法中處理消息。 (6).通過上面的第4步與第5步,就完成了客戶端向Service發送消息並且Service接收到消息的單向通信過程,即客戶端-> Service。如果要實現Service向客戶端回消息的通信過程,即Service ->客戶端,那麽前提是在客戶端中也需要像Service一樣內部維護有一個指向HandlerMessenger。當在第四步中客戶端向Service發送信息時,將MessagereplyTo屬性設置為客戶端自己的Messenger。這樣在第5ServiceHandlerhandleMessage中處理收到的消息時,可以通過MessageMessenger再向客戶端發送Message,這樣客戶端內維護的Handler對象就會收到來自於ServiceMessage,從而完成Service向客戶端發送消息且客戶端接收到消息的通信過程。


其他註意點: 


(1).當客戶端發起的bindService操作後,如果服務沒啟動,我們通過intent啟動Service,Service開始執行其生命周期,先執行onCreate回調方法,然後執行onBind回調方法,在執行onBind方法的時候,該方法返回了Service中本地serviceMessenger所對應的binder,將其返回給客戶端。如果服務啟動,繼續onRebind(),返回service中本地serviceMessenger所對應的binder。
(2).遠程應用的Service的onBind方法返回之後,會將IBinder傳入客戶端的ServiceConnection對象的onServiceConnected回調方法中,該方法的執行表明客戶端與Service建立了連接。此時,我們會根據來自於Service的IBinder初始化一個指向Service的serviceMessenger,serviceMessenger是一個遠程Messenger,我們通過對其操作,發送消息,得到處理。
(3).當用Messenger在兩個進程之間傳遞Message時,Message的obj不能設置為設置為non-Parcelable的對象,在跨進程的時候可以不使用Message的obj,用Bundle傳遞數據,setData設置Bundle數據,getData獲取Bundle數據
(4).當通過執行bindService(intent, conn, BIND_AUTO_CREATE)代碼的時候,如果intent只設置了action和category,沒有明確指明要啟動的組件,那麽該intent就是是隱式的。在Android 5.0及以上的版本中,必須使用顯式的intent去執行啟動服務,如果使用隱式的intent,則會報錯誤。 解決辦法是我們先構建一個隱式的Intent,然後通過packageManager的resolveService獲取可能會被啟動的Service的信息。如果ResolveInfo不為空,說明我們能通過上面隱式的Intent找到對應的Service,並且我們還可以獲取將要啟動的Service的package信息以及類型。然後我們需要將根據得到的Service的包名和類名,構建一個ComponentName,從而設置intent要啟動的具體的組件信息,這樣intent就從隱式變成了一個顯式的intent。然後我們可以將該顯式的intent傳遞給bindService方法去啟動服務。 http://stackoverflow.com/questions/24480069/Google-in-app-billing-illegalargumentexception-service-intent-must-be-explicit/26318757#26318757
private Intent getExplicitIapIntent() {
        PackageManager pm = mContext.getPackageManager();
        Intent implicitIntent = new Intent("com.android.vending.billing.InAppBillingService.BIND");
        List<ResolveInfo> resolveInfos = pm.queryIntentServices(implicitIntent, 0);

        // Is somebody else trying to intercept our IAP call?
        if (resolveInfos == null || resolveInfos.size() != 1) {
            return null;
        }

        ResolveInfo serviceInfo = resolveInfos.get(0);
        String packageName = serviceInfo.serviceInfo.packageName;
        String className = serviceInfo.serviceInfo.name;
        ComponentName component = new ComponentName(packageName, className);
        Intent iapIntent = new Intent();
        iapIntent.setComponent(component);
        return iapIntent;
    }




4.AIDL(Android Interface Definition Language)


  (1).使用AIDL設計遠程接口(Designing a Remote Interface Using AIDL)

由於每個應用程序都運行在自己的進程空間,並且可以從應用程序UI運行另一個服務進程,而且經常會在不同的進程間傳遞對象。在Android平臺,一個進程通常不能訪問另一個進程的內存空間,所以要想對話,需要將對象分解成操作系統可以理解的基本單元,並且有序的通過進程邊界。

通過代碼來實現這個數據傳輸過程是冗長乏味的,Android提供了AIDL工具來處理這項工作。

AIDL (Android Interface Definition Language)是一種IDL 語言,用於生成可以在Android設備上兩個進程之間進行進程間通信(IPC)的代碼。如果在一個進程中(例如Activity)要調用另一個進程中(例如Service)對象的操作,就可以使用AIDL生成可序列化的參數。

AIDL IPC機制是面向接口的,像COMCorba一樣,但是更加輕量級。它是使用代理類在客戶端和實現端傳遞數據。



(2).使用AIDL實現IPC(Implementing IPC Using AIDL)


使用AIDL實現IPC服務的步驟是:

1.         創建.aidl文件-該文件(YourInterface.aidl)定義了客戶端可用的方法和數據的接口。 2.       項目中加入你所定義的YourInterface.aidl文件 3.         實現接口-AIDL編譯器從AIDL接口文件中利用Java語言創建接口,該接口有一個繼承的命名為Stub的內部抽象類(並且實現了一些IPC調用的附加方法),我們要做的就是創建一個繼承於YourInterface.Stub的類並且實現在.aidl文件中聲明的方法。 4.         向客戶端公開接口-如果是編寫服務,應該繼承Service並且重載Service.onBind(Intent) 以返回實現了接口的對象實例


創建.aidl文件(Create an .aidl File)

AIDL使用簡單的語法來聲明接口,描述其方法以及方法的參數和返回值。這些參數和返回值可以是任何類型,甚至是其他AIDL生成的接口。 a. 必須導入所有非內置類型,哪怕是這些類型是在與接口相同的包中。 b. 如果有自定類需要使用,則需要實現Parcelable protocol c. 對於非基本數據類型,(即Parcelable類型)需要有方向指示,包括in、out和inout 最終判斷標準是:server是否在此變量中返回處理數據。 如果client不需要傳輸數據給server,client只需要處理經過server處理過後的數據, 那麽 client 和 server 都為 out  如果client只需要傳輸數據給server,而不需要處理返回的數據, 那麽client和server都為 in 如果client需要傳輸數據給server,而且需要處理返回的數據, 則client和server都為 inout
實現接口(Implementing the Interface)        a.AIDL生成了與.aidl文件同名的接口,如果使用Eclipse插件,AIDL會做為編譯過程的一部分自動運行(不需要先運行AIDL再編譯項目),如果沒有插件,就要先運行AIDL       b.生成的接口包含一個名為Stub的抽象的內部類,該類聲明了所有.aidl中描述的方法,Stub還定義了少量的輔助方法,尤其是asInterface(),通過它或以獲得IBinder(當applicationContext.bindService()成功調用時傳遞到客戶端的onServiceConnected())並且返回用於調用IPC方法的接口實例        c.要實現自己的接口,就從YourInterface.Stub類繼承,然後實現相關的方法.        d.這裏是實現接口的幾條說明:       不會有返回給調用方的異常        * 默認IPC調用是同步的。如果已知IPC服務端會花費很多毫秒才能完成,那就不要在ActivityView線程中調用,否則會引起應用程序掛起(Android可能會顯示“應用程序未響應”對話框),可以試著在獨立的線程中調用。        * AIDL接口中只支持方法,不能聲明靜態成員。
向客戶端暴露接口(Exposing Your Interface to Clients) 在完成了接口的實現後需要向客戶端暴露接口了,也就是發布服務,實現的方法是繼承 Service,然後實現以Service.onBind(Intent)返回一個實現了接口的類對象。
public class RemoteService extends Service {
...
@Override
    public IBinder onBind(Intent intent) {
        
        return mBinder;
    }

    /**
     * The IRemoteInterface is defined through IDL
     */
    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public void registerCallback(IRemoteServiceCallback cb) {
            //註冊操作
        }
    };

}


使用可打包接口傳遞參數Pass by value Parameters using Parcelables

如果有自定義類想要能過AIDL在進程之間傳遞,這一想法是可以實現的,必須確保這個類在IPC的兩端的有效性(同包同名),通常的情形是與一個啟動的服務通信。 這裏列出了使類能夠支持Parcelable4個步驟:

1.         使該類實現Parcelabel接口。

2.         實現public void writeToParcel(Parcel out) 方法,以便可以將對象的當前狀態寫入包裝對象中。

3.         增加名為CREATOR的構造器到類中,並實現Parcelable.Creator接口。

4.         最後,但同樣重要的是,創建AIDL文件聲明這個可打包的類(見下文),如果使用的是自定義的編譯過程,那麽不要編譯此AIDL文件,它像C語言的頭文件一樣不需要編譯。 AIDL會使用這些方法的成員序列化和反序列化對象。  


     客戶端訪問服務,調用對應接口,實現操作

  這裏給出了調用遠端接口的步驟: 1.         加入.aidl文件,聲明明.aidl文件中定義的接口類型的變量。

2.         實現ServiceConnection

3.         調用Context.bindService(),傳遞ServiceConnection的實現

4.         ServiceConnection.onServiceConnected()方法中會接收到IBinder對象,調用YourInterfaceName.Stub.asInterface((IBinder)service)將返回值轉換為YourInterface類型

5.         調用接口中定義的方法。應該總是捕獲連接被打斷時拋出的DeadObjectException異常,這是遠端方法唯一的異常。

6.         調用Context.unbindService()斷開連接
priv
     ServiceConnection mConnection = new ServiceConnection() {
   public void onServiceConnected(ComponentName className, IBinder service) {
        
        IRemoteService mService = IRemoteService.Stub.asInterface(service);
                try {
                mService.registerCallback(mCallback);
        } catch (RemoteException e) {
            // so there is no need to do anything here.
        }
    }

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

} };
Tags: Android Memory 初學者 開發者 Linux

文章來源:


ads
ads

相關文章
ads

相關文章

ad