1. 程式人生 > >Android跨程序通訊——AIDL使用方法淺析

Android跨程序通訊——AIDL使用方法淺析

我花了一週時間,查閱了許多的資料,在眾多AIDL介紹的文章中來來回回,反反覆覆地梳理線索,將一些概念反覆推敲,最終得出了AIDL使用的初步教程。之所以說是AIDL使用方法的初步教程,是因為IPC實在是一個很有學問的技術,我學識有限,在看羅昇陽老師有關AIDL的一系列部落格後,我認為,我最多隻能做一個使用方法的介紹,而不是一個原理分析,因為"reading the fucking source code"的能力還差得很遠。
在這一週之前,我對AIDL是完全沒有一點認識的,最初使用過一次,也是照著別人的教程一步步做,完全不知道它到底是什麼情況。但是經過了這一週的學習之後,我覺得我在AIDL的使用上已經沒有問題了,如果你也是剛好想要學習AIDL的使用,那麼我的部落格也許能給你帶來一些幫助。


首先,我們要弄清楚一下一系列問題:
1.什麼是AIDL?
2.什麼時候我需要使用AIDL?
3.怎麼使用AIDL?
4.它自動生成的同名java檔案是啥情況?
5.裡面兩個內嵌類Stub,Proxy是什麼關係?
6.它和Service怎麼扯上的?
7.Binder,IBinder是什麼玩意?IPC??
8.Parcel,老看見你,是什麼東西啊,來幹嘛的?
9.還有BinderProxy?
10.Binder驅動哪來的,運行於核心空間?
12.ServiceManager也是個Server,C/S架構?
13.好多C++程式碼上上下下啊,好煩啊。
14.什麼記憶體對映來對映去的,很疲憊啊。
......


<!--more-->


好吧,我就是在上面的問題中掙扎過了這一週,最後終於明白AIDL的好了,因為它是一種開發手段,讓我們開發者能夠無意識地,輕鬆地使用跨程序通訊,而無需寫大量地重複程式碼,因為它們都被封裝得很簡單。


進入正題,什麼是AIDL?
<blockquote>百度百科:
" Android Interface Definition Language,即Android介面定義語言。
Android系統中的程序之間不能共享記憶體,因此,需要提供一些機制在不同程序之間進行資料通訊。為了使其他的應用程式也可以訪問本應用程式提供的服務,Android系統採用了遠端過程呼叫(Remote Procedure Call,RPC)方式來實現。與很多其他的基於RPC的解決方案一樣,Android使用一種介面定義語言(Interface Definition Language,IDL)來公開服務的介面。我們知道4個Android應用程式元件中的3個(Activity、BroadcastReceiver和ContentProvider)都可以進行跨程序訪問,另外一個Android應用程式元件Service同樣可以。因此,可以將這種可以跨程序訪問的服務稱為AIDL(Android Interface Definition Language)服務。"</blockquote>
這段話我在研究AIDL時看了幾遍,沒看懂。但是現在再一看,就是這樣。
Android介面定義語言。也就是說AIDL就是.aidl檔案就是用來定義介面的,之所以使用它呢,是為了給Service提供被跨程序訪問的能力,換句話說就是讓能夠讓Service提供跨程序服務。問題來了,什麼時候Service是跨程序的,什麼時候它不是跨程序的?這個問題很關鍵,可以說是必須要清楚的一個問題。
舉個例子,當我們在一個應用裡,寫了兩個Activity:Activity1.java、Activity2.java,寫了一個Service:Service1.java。我在Activity1的Context中使用startActivity(Intent)起了Activity2,這是不是跨程序了?答案是可能是,也可能不是。當我們在應用的AndroidManifest.XML檔案中為activity或Service指定了android:process="..."屬性以後,我們就可以讓對應的Activity和Service執行在指定的程序中。如果我們不指定android:process屬性,那麼它們都執行在預設以應用包名為程序名的對應程序中。關於這個android:process屬性的介紹,可以查到很多資料,這裡不細說了,就是要有這個意識,不一定同一個應用中的元件都執行在同一個process裡。
OK,那不同應用的元件之間一定執行在不同程序中嗎?一般情況下是這樣的,但我也不能說的太絕對,因為有些特殊情況下不同應用的元件會執行在同一個程序中,這是後話,一般可以預設不同應用是執行在不同程序中的。
清楚了這一點後,我們丟擲下一個梗,我想在運行於process A的Activity中呼叫運行於process B的Service的一些物件方法,怎麼搞?OK,有個辦法是廣播。你們互相發廣播吧,這是可以的,我們需要定義一系列的廣播。舉個具體點的例子吧,否則說起來真不方便:假設有一個Service叫做“天氣助手”,裡面有一個類叫WeatherAnswer。這個WeatherAnswer中定義了一系列的方法,比如getDay(),getTemp(),getCity(),getTemp(Int).......那麼一大串的方法下來,我們使用廣播顯然有點兒吃力啊,我們要在Service的廣播接收器中為每個方法定義一個廣播項,然後還要判斷解析出傳入的引數來,這顯然是個不科學的用法。廣播更多的時候是被用來處理一些訊息,而傳遞資料這件事不該過於依賴廣播。那該怎麼辦呢?什麼ContentProvider之流的跨程序手段更是不濟,此時AIDL就呼之欲出了。
在這呼之而出之前有個問題需要在此時丟擲來:如果該Service和我們的Activity是在同一個程序裡的話,同樣的需求該如何實現?這個問題同樣可以由AIDL來解決,並且對於應用開發者來說,開發的步驟是一模一樣的,也就是說AIDL對上層開發和底層機制的隔離做得非常人性化。
終於進入正題了,我們先來約定一下。我們的Service叫做WeatherService,它在後臺跑著(先不管它是不是和Activity執行在同一個程序裡),其中有一個叫做MyAIDLImpl的物件,專門提供天氣預報應有的各種服務,它被當做跨程序通訊的Server,顧名思義,它是提供服務的物件,所以是Server。然後我們要在一個MainActivity中使用MyAIDLImpl物件提供給我們的方法int getWeather(),float getTemp(int day),String getCity(),就先這三個方法吧,由於Activity裡需要使用Server提供的服務,所以它是Client。
我們先來定義一下Server的介面,建立一個MyWeatherAIDL.aidl檔案。在裡面定義int getWeather(),float getTemp(int day),String getCity()三個介面,(我使用的eclipse,androidstudio也是類似的過程)在包裡手動建立一個MyWeatherAIDL.aidl檔案,然後用像寫一個介面一樣的語法定義方法:


<code lang="java">
package com.example.hello;


interface MyWeatherAIDL{
    int getWeather();
    float getTemp(int day);
    String getCity();
}
</code>
這樣儲存之後,eclipse為我們自動生成了對應的java檔案(aidl也就是為了方便的告訴環境,我們要什麼樣的介面,然後接下來環境說,哦,是AIDL啊,那我自動幫你生成個java檔案吧,這才是你真正要的,你自己寫的話太累了,just這個目的)。生成的java檔案位於/gen/com/example/hello下,開啟來看看eclipse都為我們做了些什麼:


<code lang="java">
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: E:\\eclipse_workspace\\Hello\\src\\com\\example\\hello\


\MyWeatherAIDL.aidl
*/
package com.example.hello;


public interface MyWeatherAIDL extends android.os.IInterface
{
    /** Local-side IPC implementation stub class. */
    public static abstract class Stub extends android.os.Binder implements
        com.example.hello.MyWeatherAIDL{


        private static final java.lang.StringDESCRIPTOR = "com.example.hello.MyWeatherAIDL";


        /** Construct the stub at attach it to the interface. */
        public Stub(){
            this.attachInterface(this, DESCRIPTOR);
        }
        /**
        * Cast an IBinder object into an com.example.hello.MyWeatherAIDL interface,
        * generating a proxy if needed.
        */


        public static com.example.hello.MyWeatherAIDL asInterface
            (android.os.IBinder obj){
            if ((obj==null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin!=null)&amp;&amp;(iin instanceof 
                com.example.hello.MyWeatherAIDL))){
                return ((com.example.hello.MyWeatherAIDL)iin);
            }
            return new com.example.hello.MyWeatherAIDL.Stub.Proxy(obj);
        }


        @Override
        public android.os.IBinder asBinder(){
            return this;
        }


        @Override public boolean onTransact(int code, android.os.Parcel data, 
            android.os.Parcel reply, int flags) throws android.os.RemoteException{
            switch (code){
                case INTERFACE_TRANSACTION:
                {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_getWeather:
                {
                    data.enforceInterface(DESCRIPTOR);
                    int _result = this.getWeather();
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
                case TRANSACTION_getTemp:
                {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    float _result = this.getTemp(_arg0);
                    reply.writeNoException();
                    reply.writeFloat(_result);
                    return true;
                }
                case TRANSACTION_getCity:
                {
                    data.enforceInterface(DESCRIPTOR);
                    java.lang.String _result = this.getCity();
                    reply.writeNoException();
                    reply.writeString(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }


        private static class Proxy implements 
            com.example.hello.MyWeatherAIDL{
            private android.os.IBinder mRemote;


            Proxy(android.os.IBinder remote){
                mRemote = remote;
            }


            @Override
            public android.os.IBinder asBinder(){
                return mRemote;
            }


            public java.lang.String getInterfaceDescriptor(){
                return DESCRIPTOR;
            }


            @Override public int getWeather() throws 
                android.os.RemoteException{
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getWeather, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }


            @Override public float getTemp(int day) throws 
                android.os.RemoteException{
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                float _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(day);
                    mRemote.transact(Stub.TRANSACTION_getTemp, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readFloat();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }


            @Override public java.lang.String getCity() throws 
                android.os.RemoteException{
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                java.lang.String _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    mRemote.transact(Stub.TRANSACTION_getCity, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readString();
                }
                finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }


        static final int TRANSACTION_getWeather = 
            (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
        static final int TRANSACTION_getTemp = 
            (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
        static final int TRANSACTION_getCity = 
            (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
    }


    public int getWeather() throws android.os.RemoteException;
    public float getTemp(int day) throws android.os.RemoteException;
    public java.lang.String getCity() throws android.os.RemoteException;
}
</code>


我們粗略的看看該檔案,首先它是和我們的aidl檔案同名的一個java介面:MyWeather。其中包含了我們在aidl中定義的三個方法:


<code lang="java">
public int getWeather() throws android.os.RemoteException;
public float getTemp(int day) throws android.os.RemoteException;
public java.lang.String getCity() throws android.os.RemoteException;
</code>


它們都可能會丟擲android.os.RemoutException異常。然後Interface中有一個內嵌類叫:
<code lang="java">
public static abstract class Stub extends android.os.Binder implements com.example.hello.MyWeatherAIDL
</code>
很複雜對不對,首先它是個內嵌類因為static關鍵字,故而它和MyWeatherAIDL沒太多聯絡實際上,它並不是一個內部類。然後呢,它繼android.os.Binder類,這個類是幹什麼的?我們開啟Binder的原始碼來搜尋點蛛絲馬跡:
<blockquote>/**
31 * Base class for a remotable object, the core part of a lightweight
32 * remote procedure call mechanism defined by {@link IBinder}.
33 * This class is an implementation of IBinder that provides
34 * standard local implementation of such an object.
35 *
36 * &lt;p&gt;Most developers will not implement this class directly, instead using the
37 * &lt;a href="{@docRoot}guide/components/aidl.html"&gt;aidl&lt;/a&gt; tool to describe the desired
38 * interface, having it generate the appropriate Binder subclass. You can,
39 * however, derive directly from Binder to implement your own custom RPC
40 * protocol or simply instantiate a raw Binder object directly to use as a
41 * token that can be shared across processes.
42 *
43 * &lt;p&gt;This class is just a basic IPC primitive; it has no impact on an application's
44 * lifecycle, and is valid only as long as the process that created it continues to run.
45 * To use this correctly, you must be doing so within the context of a top-level
46 * application component (a {@link android.app.Service}, {@link android.app.Activity},
47 * or {@link android.content.ContentProvider}) that lets the system know your process
48 * should remain running.&lt;/p&gt;
49 *
50 * &lt;p&gt;You must keep in mind the situations in which your process
51 * could go away, and thus require that you later re-create a new Binder and re-attach
52 * it when the process starts again. For example, if you are using this within an
53 * {@link android.app.Activity}, your activity's process may be killed any time the
54 * activity is not started; if the activity is later re-created you will need to
55 * create a new Binder and hand it back to the correct place again; you need to be
56 * aware that your process may be started for another reason (for example to receive
57 * a broadcast) that will not involve re-creating the activity and thus run its code
58 * to create a new Binder.&lt;/p&gt;
59 *
60 * @see IBinder
61 */</blockquote>
檔案一開始的說明註釋如上說,大概有個數了,原來Binder類啊繼承自IBinder類(= =),它是遠端物件的雞肋,不是基類。它是輕量級遠端過程呼叫機制的核心部分。這個類可以給你提供一個遠端物件的標準本地實現。我們地球人一般不手動寫這個類,而是使用一個叫做aidl的工具來描述出你夢想中的介面,然後我們會幫你實現這個夢想,合理地。你可以,但是,直接去從Binder類實現你自己的自定義RPC協議或者簡單地實現一個未加工過的Binder物件,把它當做一個token,來進行跨程序分享。這個Binder類是一個基礎的IPC原生類,它對applicant的生命週期沒有影響,它僅當建立它的程序還活著的時候才有效。所以為了正確地使用它,你必須在一個頂級app元件裡明確地讓系統知道,要讓程序活下去。你必須時刻在腦子中記住的是,在哪些情況下你的程序會不辭而別,並且在這種情況下你需要在程序重啟時重新建立一個新的Binder並且attach it,(好吧attach這個詞是那麼傳神我無法用Chinese表達)。舉個例子,如果你在Activity中使用Binder,你的activity程序在開始前隨時都有可能被槍斃,如果activity重新啟動了你得新建一個Binder,把它放回原處(正確處)。你需要知道你的程序可能會因為各種各樣的原因被啟動比如說接到廣播,在這情況下是不需要重建activity的,因此你的重建Binder的程式碼可能不會被跑到,這是危險的喲。


OK,以上的翻譯真是體力活,但是我們已經對Binder有了個粗略的瞭解,它就是遠端物件的雞肋嘛,是雞肋,是基類。OK,那我們的Stub就是一個繼承自遠端物件雞肋的抽象類,它同時實現了MyWeatherAIDL介面。也就是說如果繼承自該類的話,那麼我們必須要實現MyWeatherAIDL的三個方法(我們的夢想介面),同時,它就是個真正的遠端物件了,它就是我們的Server(是Server不是Service)。那就讓我們來實現它吧,建立一個WeatherService.java,來作為Server的保鮮容器,重寫裡面的onBind方法:
<code lang="java">
@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    return new MyWeatherAIDL.Stub() {


        @Override
        public int getWeather() throws RemoteException {
            // TODO Auto-generated method stub
            return 100;
        }


        @Override
        public float getTemp(int day) throws RemoteException {
            // TODO Auto-generated method stub
            return day;
        }
    
        @Override
        public String getCity() throws RemoteException {
            // TODO Auto-generated method stub
            return "wuxi";
        }
    };
}
</code>


OK,return的值是一個IBinder,IBinder是一個介面,故而我們返回的一定是一個實現了IBinder的物件,然後向上轉型了對吧。返回值是new MyWeatherAIDL.Stub()物件,這是匿名內部類的寫法比較簡潔省事,(如果你對此不是很熟悉,請購買《Thinking in java》,購買地址:http://product.dangdang.com/1173405126.html,並且熟讀其中的匿名內部類一章)。我們當然也可以用一種看得更加清楚明瞭的方式:
<code lang="java">
class MyAIDLImpl extends MyWeatherAIDL.Stub{


    @Override
    public int getWeather() throws RemoteException {
        // TODO Auto-generated method stub
        return 100;
    }


    @Override
    public float getTemp(int day) throws RemoteException {
        // TODO Auto-generated method stub
        return day;
    }


    @Override
    public String getCity() throws RemoteException {
        // TODO Auto-generated method stub
        return "wuxi";
    }
}


@Override
public IBinder onBind(Intent intent) {
    // TODO Auto-generated method stub
    MyAIDLImpl myImpl=new MyAIDLImpl();
    return myImpl;
}
</code>


我們在Service中實現了一個MyAIDLImpl類,它就是Server,然後呢,裡面具體實現了我們的夢想介面,也就是現實實現。然後我們在onBind方法裡將其返回了,它經歷了向上轉型為MyAIDL.Stub再轉型為Binder再轉型為IBinder的過程。可能有人發現這樣向上轉型了有個p用,現實實現的方法無法呼叫了啊,是的別急,會給你向下轉回來的。
接下來我們要用這個Server了吧,誰來用,Client來用,Client是誰,隨便你,都可以。我們這裡就選擇最常規的Activity吧:


<code lang="java">
Intent serviceIntent=new Intent(this,com.example.hello.WeatherService.class);
bindService(serviceIntent, conn, BIND_AUTO_CREATE);
</code>
在你想使用這個Server前先繫結它的容器Service,使用Context.bindService(Intent,ServiceConnection,int)方法,Intent是指明你的Service的“線索”,而ServiceConnection是我們馬上要實現的一個介面,其中包含了一些關於bindService時會被呼叫的回撥,而最後的int則是一些繫結選項,這個選項我還沒有細究。以後出Service部落格時我再細究。ok,這樣呼叫bindService的過程中,Service的onBind方法被呼叫,IBinder被獲取,並且傳給了ServiceConnection實現的onServiceConnected回撥方法中,來看一下ServiceConnection實現:
<code lang="java">
ServiceConnection conn=new ServiceConnection() {


    @Override
    public void onServiceDisconnected(ComponentName name) {
        // TODO Auto-generated method stub
    
    }


    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        // TODO Auto-generated method stub


    }
};
</code>


有兩個回撥,一看就知道意思,一個是Service斷連後觸發,一個是Service連線後觸發。onServiceConnected的方法中傳入了一個IBinder service,這個就是Server。Okay,只能說它是Server,但不一定是Server的真身,我們過會會了解到為什麼這麼說,暫且我們就把它當做是回來的MyAIDLImpl物件吧。
我們在MyActivity中建立一個MyWeatherAIDL物件的引用:MyWeatherAIDL mWeatherAnswer; 然後我們在onServiceConnected中這麼做:
<code lang="java">
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
    // TODO Auto-generated method stub
    mWeatherAnswer=MyWeatherAIDL.Stub.asInterface(service);
}
</code>
這樣,通過MyWeatherAIDL.Stub的asInterface方法將service轉換成了MyWeatherAIDL物件,(我說過會給它轉回來的),ok,這樣我們就可以安心地使用了我們的夢想介面的方法了,getXX,getXX,然後Server就回不斷地回答你的問題。


好了,AIDL就是這麼簡單。
但是到底發生了什麼?
asInterface只是簡單地向下轉型再向上轉型麼?總覺得AIDL這麼方便我好像什麼都還沒做有點良心不安誒。。。而且自動生成的夢想介面java檔案那麼多的東西是幹嘛的,不看看有點小緊張誒。既然我們這麼想,那就一窺這神奇的世界吧。


依舊是看那個自動生成的MyWeatherAIDL.java檔案。不用太費心思翻上去看了,我會把它一段段貼上下來的,貼心的博主萬歲。
首先我們直奔的一定是Stub.asInterface方法:
<code lang="java">
/**
* Cast an IBinder object into an com.example.hello.MyWeatherAIDL interface,
* generating a proxy if needed.
*/


public static com.example.hello.MyWeatherAIDL asInterface(android.os.IBinder 
obj){
    if ((obj==null)) {
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin!=null)&amp;&amp;(iin instanceof 
            com.example.hello.MyWeatherAIDL))) {
        return ((com.example.hello.MyWeatherAIDL)iin);
    }
    return new com.example.hello.MyWeatherAIDL.Stub.Proxy(obj);
}
</code>


方法的說明註釋的意思是,該方法把一個IBinder物件轉型為一個MyWeatherAIDL物件,必要的時候生成一個Proxy,即代理。
程式碼中看,首先呼叫了傳入的IBinder:obj的IInterface queryLocalInterface(String)。然後根據其返回的IInterface iin是否是MyWeatherAIDL介面的例項來決定返回向下轉型成MyWeatherAIDL的物件,還是一個包裝進new com.example.hello.MyWeatherAIDL.Stub.Proxy(obj);的物件。我看到這裡的時候非常疑惑,首先什麼是queryLocalInterface,傳入的String是
private static final java.lang.String DESCRIPTOR = "com.example.hello.MyWeatherAIDL";
這是個編譯期常量。我先直接說明一下,首先,queryLocalInterface方法呢,是根據提供的Interface名,來檢視一下你這個obj(IBinder)是否是一個本地程序中的介面物件例項,如果是呢,當然可以直接強制型別轉換,返回引用。但是也可能不是本地程序中的藉口的物件例項,比如說我在其他程序中定義的MyWeatherAIDL例項(Server),它在onBind返回時,返回的其實,這裡是關鍵,它其實是一個BinderProxy。顧名思義,它沒給你一個Binder真身的引用,它只是給了你一個代理。而這個BinderProxy是何方神聖?它被定義於
/frameworks/base/core/java/android/os/Binder.java
檔案中,也就是說和Binder在同一個檔案裡,但它並不是Binder的內部類哦。
<code lang="java">
final class BinderProxy implements IBinder
</code>
它也實現了IBinder介面,同時它的queryLocalInterface方法:
<code lang="java">
public IInterface queryLocalInterface(String descriptor) {
    return null;
}
</code>
該方法是不分情況直接返回null的,也就是說,在Stub的asInterface方法中,只要obj是一個BinderProxy,立刻建立MyWeatherAIDL.Stub.Proxy物件出來封裝這個BinderProxy。


好了,我們可以回頭梳理一下了,我們在MyWeatherService的onBind方法中建立了我們的Server:MyAIDLImpl的物件,然後將它作為IBinder返回了,Client的bindService方法中獲取了一個IBinder傳遞給了OnServiceConnected方法,但是這個IBinder不一定是Server的引用:
(1)如果MyWeatherService是和Client同進程的話,那麼返回的是MyAIDLImpl物件的引用。
(2)如果MyWeatherService在另一個程序中執行著的話,那麼此時IBinder是一個BinderProxy。


應該說的很清楚了。那麼MyWeatherAIDL.Stub.asInterface方法也很明確表示,(1)的情況我們直接使用的就是這個Server的引用,這種情況下我們是在直接呼叫方法okay?這不是IPC,僅僅是直接呼叫方法。(2)的情況才是真正的IPC,我們獲取的只是一個代理,它的大哥在另一個process中逍遙呢,所以我們沒法直接聯絡Server,只能靠它的代理來溝通問題。
我們下面就可以專注於代理的情況了。我們試著讓Client呼叫一下,其實我覺得我們在Activity中建立的mWeatherAnswer是一個比Activity更加精確的Client。mWeatherAnswer.getTemp(1); 這樣呼叫發生了什麼?首先我們已經知道,mWeatherAnswer是一個MyWeatherAIDL.Stub.Proxy。我們聚焦一下:
<code lang="java">
private static class Proxy implements com.example.hello.MyWeatherAIDL
{
    private android.os.IBinder mRemote;
    Proxy(android.os.IBinder remote){
        mRemote = remote;
    }


    @Override
    public android.os.IBinder asBinder(){
        return mRemote;
    }


    public java.lang.String getInterfaceDescriptor(){
        return DESCRIPTOR;
    }


    @Override public int getWeather() throws android.os.RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        int _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getWeather, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readInt();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }


    @Override public float getTemp(int day) throws 
        android.os.RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        float _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            _data.writeInt(day);
            mRemote.transact(Stub.TRANSACTION_getTemp, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readFloat();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }


    @Override public java.lang.String getCity() throws 
        android.os.RemoteException{
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        java.lang.String _result;
        try {
            _data.writeInterfaceToken(DESCRIPTOR);
            mRemote.transact(Stub.TRANSACTION_getCity, _data, _reply, 0);
            _reply.readException();
            _result = _reply.readString();
        }
        finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }
}
</code>


它是Stub的內嵌類,它也實現了MyWeatherAIDL介面,所以它也需要實現夢想介面中的方法,它已經自動為我們生成好了這三個getXX形式的方法。我們就拿float getTemp(int day)來舉例吧。首先它先通過android.os.Parcel.obtain();獲取了兩個Parcel物件:_data,_reply。這個_data的作用是用來將Client呼叫getTemp(int day)時傳入的引數列表給封裝起來,而_reply則是為Server處理呼叫後的返回結果而準備的。_data先寫入TOKEN,然後通過Parcel類的writeXX型別方法將引數列表中的引數依次按照對應型別寫入,此處只有一個int引數,所以是writeInt(day)。然後呼叫mRemote.transact(Stub.TRANSACTION_getTemp, _data, _reply, 0);mRemote就是被Proxy封裝起來的那個obj哦,也就是BinderProxy,別忘記了。讓代理呼叫transact方法,其中,Stub.TRANSACTION_getTemp定義在了Stub類中,是編譯期常量,每個夢想介面的方法都有一個對應的int型別的方法標識:
<code lang="java">
static final int TRANSACTION_getWeather = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_getTemp = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getCity = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
</code>
這個方法標識引數是要告訴Server,我是請求呼叫這個那個方法的。然後transact方法再傳入_data用於告訴Server,引數在這個Parcel裡啊。然後傳入_reply引數,高速Server,返回的結果放到這個Parcel裡即可。最後一個0,這個嘛,暫時我也還沒研究得這麼細緻,以後再說吧。


OK,transact方法這麼一呼叫會發生什麼呢?IPC。
我們看一下IBinder類對transact的說明,注意,BinderProxy是一個IBinder,我們要看的是IBinder對transact方法的說明:
沒有sorry,該方法僅僅是個介面,具體實現具體分析,所以它僅僅說了幾個引數幹嘛的。那就去BinderProxy去看看有什麼線索,很遺憾,它沒有說明註釋,但是它的方法很簡短:
<code lang="java">
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws    RemoteException {
    Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
    return transactNative(code, data, reply, flags);
}
</code>
先讓Binder處理了一下傳入的_data這個Parcel,然後transactNative方法,一溜煙把函式代號,兩個parcel傳到native方法呼叫去了。我們知道,這些資料都去了底下C++的程式碼層裡了。就好像打了個地洞,直奔地核了一樣。我暫時不想討論到了C++層會發生些什麼。請重新看一下我們這篇部落格的標題,請大聲讀一遍,AIDL使用方法淺析,方法淺析,淺析,析。。。。。。請不要感到難過,我們也許還能找到些蛛絲馬跡,請看一下IBinder一開始的一段說明註釋:
<blockquote>......
*The key IBinder API is {@link #transact transact()} matched by
30 * {@link Binder#onTransact Binder.onTransact()}. These
31 * methods allow you to send a call to an IBinder object and receive a
32 * call coming in to a Binder object, respectively. This transaction API
33 * is synchronous, such that a call to {@link #transact transact()} does not
34 * return until the target has returned from
35 * {@link Binder#onTransact Binder.onTransact()}; this is the
36 * expected behavior when calling an object that exists in the local
37 * process, and the underlying inter-process communication (IPC) mechanism
38 * ensures that these same semantics apply when going across processes.
......</blockquote>
IBinder的這段說明很長很精彩,我只截取了一段最關鍵的關於transact的說法。IBinder的transact方法和Binder物件的onTransact配套。這些方法允許你對IBinder物件傳送一個呼叫,然後從一個Binder物件中獲取一個呼叫,分別地。(這個分別地告訴我們Binder物件和IBinder物件不是同一個物件,其實也就是暗指IBinder就是BinderProxy就是Client,而Binder就是Server)這個transaction API是同步的,transact呼叫完了以後,必須等到Binder物件的onTransact方法返回一個才能繼續往下走,否則就阻塞在那裡等待。這是我們在本地程序中呼叫一個物件來使用IPC機制,所期望的一種行為。並且,這種手段確保了我們在用IPC機制進行跨程序通訊時,使用和本地呼叫一樣的語法。(就好像直接呼叫了一個物件的一個方法一樣,而完全感覺不到跨程序呼叫的存在)


我們已經知道了我們想知道的東西。提取一下上面的資訊就是,我們BinderProxy(Client)的transact呼叫,會引發Binder(Server)的onTransact回撥,然後Client等待onTransact做完,才能繼續往下走,onTransact做完時,函式返回結果已經存在了_reply裡,我們接著往下看一下:
<code lang="java">
_reply.readException();
_result = _reply.readString();
</code>
這就是讀一下結果,是不是有異常啊,返回的結果存在_result變數中,繼續最後的工作:
<code lang="java">
finally {
    _reply.recycle();
    _data.recycle();
}
return _result;
</code>
finally裡進行了Parcel的釋放工作,最後將函式結果返回。此時Client這裡發生的一切我們就已經很明確了。那Server那邊onTransact方法做了什麼呢?我們看一下另一個程序中的Server,它是一個MyWeatherAIDL.Stub子類:
<code lang="java">
@Override public boolean onTransact(int code, android.os.Parcel data, 
    android.os.Parcel reply, int flags) throws android.os.RemoteException{
    switch (code){
        case INTERFACE_TRANSACTION:
        {
            reply.writeString(DESCRIPTOR);
            return true;
        }
        case TRANSACTION_getWeather:
        {
            data.enforceInterface(DESCRIPTOR);
            int _result = this.getWeather();
            reply.writeNoException();
            reply.writeInt(_result);
            return true;
        }
        case TRANSACTION_getTemp:
        {
            data.enforceInterface(DESCRIPTOR);
            int _arg0;
            _arg0 = data.readInt();
            float _result = this.getTemp(_arg0);
            reply.writeNoException();
            reply.writeFloat(_result);
            return true;
        }
        case TRANSACTION_getCity:
        {
            data.enforceInterface(DESCRIPTOR);
            java.lang.String _result = this.getCity();
            reply.writeNoException();
            reply.writeString(_result);
            return true;
        }
    }
    return super.onTransact(code, data, reply, flags);
}
</code>
首先,它先要看看,你Client企圖呼叫我的什麼那個方法。然後進行入對應的case。我們就進入TRANSACTION_getTemp條目。讀取了DESCTIPTOR,再挨個將每個引數列表中的引數讀取出來,然後輕而易舉地呼叫float this.getTemp(int)方法獲取一個返回值,這個方法的具體實現就是我們在MyWeatherService中實現的那個實現,就是這樣。然後呢,將返回值寫到_reply裡,然後writeNoException(),return true,表示沒有丟擲異常,正常執行了。
一個IPC過程就這樣順利輕鬆地做完了。
好了現在你一定已經明白了AIDL是什麼了,也知道它只是一個幫助我們快速簡單地進行IPC通訊的手段,對於開發者來說,一大段固定的C/S協議程式碼無需我們一行行手動書寫。但是,我們必須得知道這其中是有故事的。
但是千萬別認為AIDL很簡單,或者準確地說,別認為Binder很簡單,它裡面非常複雜,到了C++那一層有很多很多的學問,具體看看羅昇陽前輩的非常詳盡的N篇關於AIDL,IPC,Binder原理的系列文章吧:,非常有難度。我這裡這篇博文僅僅起到讓完全不懂AIDL的應用開發者能夠理解AIDL為何物,如何使用,的作用而已。
最後,說明一下我並沒有演示AIDL執行的例項,這個請讀者自己實現吧,以後我也會寫一篇具體的使用演示的部落格。如果你發現了本文中的謬誤,請一定一定指出來,這樣我也可以進步,也可以和您一起探討,當然,有問題我也會盡我微薄之學,予以回覆,thanks。
最後,也會跟隨羅昇陽前輩的腳步,將Binder這個IPC核心給深入剖析一下。
最後,他媽的轉載請註明出處!(╯‵□′)╯︵┻━┻