AIDL 的使用和代碼分析

一.相關(guān)介紹

在 Android 系統(tǒng)中,進(jìn)程間通信 (IPC) 是一種很重要的機(jī)制予弧。IPC 產(chǎn)生的原因是某些情況下需要在兩個進(jìn)程之間進(jìn)行一些數(shù)據(jù)的交換刮吧。而在深入學(xué)習(xí) Android 的過程中難免會遇到 IPC 的相關(guān)問題,比如常見的有在自己的應(yīng)用程序中讀取手機(jī)聯(lián)系人的信息杀捻,這就涉及到 IPC 了水醋。因為自己的應(yīng)用程序是一個進(jìn)程拄踪,通訊錄也是一個進(jìn)程惶桐,只不過獲取通訊錄的數(shù)據(jù)信息是通過 Content Provider 的方式來實(shí)現(xiàn)的姚糊。

對于初學(xué)者來說,在一開始接觸 IPC 時可能會摸不著頭腦肠槽,因為網(wǎng)上很多博客在講 Android IPC 時通常都是長篇大論秸仙,沒有從例子著手寂纪±痰埃基于以上種種原因以及希望對 AIDL 有一個更深入的理解拟杉,本篇博文就誕生了啼染。在 Android 系統(tǒng)中,IPC 的方式有很多種卦洽,比如有 Messenger 该窗、AIDL 和 ContentProvider 等酗失。我們今天就來講講其中的 AIDL ,AIDL 也是比較常見和經(jīng)常使用的一種 IPC 方式拖刃。希望讀者在看完本篇之后對于 AIDL 有一個比較深入的理解兑牡。

什么是 AIDL均函?

AIDL 的全稱是 Android Interface Definition Language(即 Android 接口定義語言)经柴。

AIDL是Binder的實(shí)例坯认。

AIDL的使用實(shí)例:

我們來模擬一下需要進(jìn)行 IPC 的情況牛哺,現(xiàn)在有客戶端和服務(wù)端引润,客戶端通過 AIDL 來和服務(wù)端進(jìn)行 IPC 淳附。我們假定現(xiàn)在客戶端需要傳一個 Person 類的對象給服務(wù)端奴曙,之后服務(wù)端回傳給客戶端一個 Person 類的集合。

1.服務(wù)端的相關(guān)代碼

以下 Person.aidl 文件:

parcelable Person;

注意在 IPC 機(jī)制中傳遞的自定義對象需要序列化坤溃,所以要實(shí)現(xiàn) Parcelable 接口。在 AIDL 文件中使用parcelable關(guān)鍵字聲明汁政。有了 Person.aidl 之后烂完,我們就要創(chuàng)建 AIDL 接口了。

interface AddPersonInter {
List<Person> addPerson(in Person person);
}

在 IMyAidlInterface.aidl 里嘶窄,主要聲明一個用于添加 Person 對象的抽象方法吻谋。另外漓拾,需要注意以下幾點(diǎn):

1.Person 類需要手動去 import ,在 AIDL 文件中不能自動導(dǎo)包低千;

2.在addPerson方法里需要聲明參數(shù)是 in 的,用來表示該參數(shù)是傳入的救拉。除了 in 之外拂铡,還有 out 和 inout 斗锭;

下面我們要創(chuàng)建一個 Service 用于和客戶端進(jìn)行 IPC 岖是。這里還要把該 Service 運(yùn)行在一個新的進(jìn)程里。我們只要在 AndroidManifest.xml 中聲明android:process=":remote"就行了。

public class MyService extends Service {
private static final String TAG = "MyService";

private List<Person> persons = new ArrayList<>();

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
    Log.e("aaaa", "binder綁定成功");
    return binder;
}

private final IBinder binder = new AddPersonInter.Stub() {
    @Override
    public List<Person> addPerson(Person person) throws RemoteException {
        synchronized (persons) {
            persons.add(person);
            Log.e("aaaaa", "服務(wù)端  name----" + person.getName() + "       age===" + person.getAge());
            return persons;
        }
    }
};
}

在上面的代碼中我們可以看到在onBind(Intent intent)方法中返回了 mBinder 陆错,而客戶端正是通過這個 mBinder 來和服務(wù)端進(jìn)行 IPC 的对嚼。mBinder 是 IMyAidlInterface.Stub 匿名類的對象,IMyAidlInterface.Stub 其實(shí)是一個抽象類磨确,繼承自 Binder ,實(shí)現(xiàn)addPerson方法。這里要注意以下媳瞪,在addPerson的方法中需要將 persons 同步,這是因為在服務(wù)端 AIDL 是運(yùn)行在 Binder 線程池中的乍丈,有可能會有多個客戶端同時連接察蹲,這時候就需要同步以防止數(shù)據(jù)出錯宗收。

2.客戶端的相關(guān)代碼

客戶端需要將服務(wù)端的aidl文件夾整體復(fù)制到客戶端,并將用到到的java類Person.java復(fù)制到客戶端挑宠,
注意包名一致诡挂,不然編譯會報錯奴璃。

public class Main2Activity extends AppCompatActivity {

TextView tv;
private AddPersonInter addPersonInter;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv=this.findViewById(R.id.tv);
    // 啟動服務(wù)端的服務(wù)雳旅,并進(jìn)行綁定
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.seventeenok.test", "com.seventeenok.test.MyService"));
    bindService(intent, conn, Context.BIND_AUTO_CREATE);

    tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        List<Person> list = addPersonInter.addPerson(new Person("lizhi", 24));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    });
}

private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        addPersonInter = AddPersonInter.Stub.asInterface(service);
    }

    @Override
    public void onServiceDisconnected(ComponentName name) {
        addPersonInter = null;
    }
};
}

AIDL 的流程基本上就是這樣的型豁。通過這個簡單的例子驼壶,相信對于 AIDL 有了一個初步的了解。下面我們就要進(jìn)行 AIDL 的代碼分析。

3.項目的整體目錄結(jié)構(gòu):
aaa.png

AIDL代碼分析

工程中的 gen 目錄下找到對應(yīng) AIDL 編譯后的文件:

public interface AddPersonInter extends android.os.IInterface {
/**
 * Local-side IPC implementation stub class.
 */
public static abstract class Stub extends android.os.Binder implements com.seventeenok.test.AddPersonInter {
    private static final java.lang.String DESCRIPTOR = "com.seventeenok.test.AddPersonInter";

    /**
     * Construct the stub at attach it to the interface.
     */
    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    /**
     * Cast an IBinder object into an com.seventeenok.test.AddPersonInter interface,
     * generating a proxy if needed.
     */
    public static com.seventeenok.test.AddPersonInter asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.seventeenok.test.AddPersonInter))) {
            return ((com.seventeenok.test.AddPersonInter) iin);
        }
        return new com.seventeenok.test.AddPersonInter.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_addPerson: {
                data.enforceInterface(DESCRIPTOR);
                com.seventeenok.test.Person _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.seventeenok.test.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                java.util.List<com.seventeenok.test.Person> _result = this.addPerson(_arg0);
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.seventeenok.test.AddPersonInter {
        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 java.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.seventeenok.test.Person> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((person != null)) {
                    _data.writeInt(1);
                    person.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.seventeenok.test.Person.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }

    static final int TRANSACTION_addPerson = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}

public java.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException;

}

可以看到編譯后的 AddPersonInter.aidl 變成了一個接口,繼承自 IInterface 。在 AddPersonInter接口中我們發(fā)現(xiàn)主要分成兩部分結(jié)構(gòu):抽象類 Stub 和原來 aidl 中聲明的addPerson方法凰兑。

重點(diǎn)在于 Stub 類滩报,下面我們來分析一下。從 Stub 類中我們可以看到是繼承自 Binder 并且實(shí)現(xiàn)了 AddPersonInter接口侣姆。 Stub 類的基本結(jié)構(gòu)如下:

asInterface(android.os.IBinder obj)方法川蒙;

asBinder()方法昼牛;

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法恬汁;

靜態(tài)類Proxy导狡,主要方法是addPerson(com.seventeenok.test.Person person)看彼;

靜態(tài)常量TRANSACTION_addPerson标锄;

asInterface(android.os.IBinder obj)

我們先從asInterface(android.os.IBinder obj)方法入手践剂,在上面的代碼中可以看到,主要的作用就是根據(jù)傳入的Binder對象轉(zhuǎn)換成客戶端需要的 AddPersonInter接口。如果服務(wù)端和客戶端處于同一個進(jìn)程匕争,那么該方法得到的就是服務(wù)端 Stub 對象本身跑杭,也就是上面 AIDL 例子 MyService 中的 mBinder 對象;否則返回的是系統(tǒng)封裝后的 Stub.Proxy ,也就是一個代理類,在這個代理中實(shí)現(xiàn)跨進(jìn)程通信。

asBinder()

該方法就是返回當(dāng)前的 Binder 對象蚯舱。

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

在onTransact方法中兄裂,根據(jù)傳入的 code 值會去執(zhí)行服務(wù)端相對應(yīng)的方法腥泥。其中靜態(tài)變量TRANSACTION_addPerson就是其中的 code 值之一(在 AIDL 文件中聲明的方法有多少個就有多少個對應(yīng)的 code )。其中 data 就是服務(wù)端方法中所需要的參數(shù),執(zhí)行完后帅戒,最后把方法的返回結(jié)果放入 reply 中傳遞給客戶端瞎访。若該方法返回 false ,那么客戶端請求失敗写烤。

Proxy中的addPerson(com.seventeenok.test.Person person)

Proxy 類是實(shí)現(xiàn)了 AddPersonInter接口暂衡,把其中的addPerson方法進(jìn)行了重寫读恃。在方法中一開始創(chuàng)建了兩個 Parcel 對象,其中一個用來把方法的參數(shù)裝入,然后調(diào)用transact方法執(zhí)行服務(wù)端的代碼,執(zhí)行完后把返回的結(jié)果裝入另外一個 Parcel 對象中返回。

看完上面方法的介紹谤草,我們回過頭來看看 AIDL 例子中實(shí)現(xiàn)的流程温学。在客戶端中通過 Intent 去綁定一個服務(wù)端的 Service 。在onServiceConnected(ComponentName name, IBinder service)方法中通過返回的 service 可以得到對應(yīng)的 AIDL 接口的實(shí)例黄痪。這是調(diào)用了asInterface(android.os.IBinder obj)方法來完成的桅打。

客戶端在onServiceConnected(ComponentName name, IBinder service)中得到的 service 正是服務(wù)端中的 mBinder 是嗜。當(dāng)客戶端調(diào)用 AIDL 接口時丽柿,AIDL 通過 Proxy 類中的addPerson來調(diào)用transact方法,transact方法又會去調(diào)用服務(wù)端的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法涂召。onTransact方法是運(yùn)行在服務(wù)端的 Binder 線程池中的果正。在onTransact中根據(jù) code 執(zhí)行相關(guān) AIDL 接口的方法秋泳,方法的參數(shù)從 data 中獲取。執(zhí)行完畢之后把結(jié)果裝入 reply 中返回給客戶端。 AIDL 的流程基本上就是這樣子了。

總結(jié)

AIDL 在 Android IPC 機(jī)制中算得上是很重要的一部分饲握,AIDL 主要是通過 Binder 來實(shí)現(xiàn)進(jìn)程通信的。當(dāng)然频丘,上面只是簡單的例子分析AIDL的整個流程,并沒有涉及到死亡代理亮钦、權(quán)限驗證等功能馆截,有想法的同學(xué)可以深入研究下。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末蜂莉,一起剝皮案震驚了整個濱河市蜡娶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌映穗,老刑警劉巖窖张,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異蚁滋,居然都是意外死亡宿接,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進(jìn)店門辕录,熙熙樓的掌柜王于貴愁眉苦臉地迎上來睦霎,“玉大人,你說我怎么就攤上這事走诞「迸” “怎么了?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵蚣旱,是天一觀的道長碑幅。 經(jīng)常有香客問我戴陡,道長,這世上最難降的妖魔是什么沟涨? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任恤批,我火速辦了婚禮,結(jié)果婚禮上裹赴,老公的妹妹穿的比我還像新娘喜庞。我一直安慰自己,他們只是感情好篮昧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布赋荆。 她就那樣靜靜地躺著笋妥,像睡著了一般懊昨。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上春宣,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天酵颁,我揣著相機(jī)與錄音,去河邊找鬼月帝。 笑死躏惋,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的嚷辅。 我是一名探鬼主播簿姨,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼簸搞!你這毒婦竟也來了扁位?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤趁俊,失蹤者是張志新(化名)和其女友劉穎域仇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體寺擂,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡暇务,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了怔软。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片垦细。...
    茶點(diǎn)故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖挡逼,靈堂內(nèi)的尸體忽然破棺而出括改,到底是詐尸還是另有隱情,我是刑警寧澤挚瘟,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布叹谁,位于F島的核電站饲梭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏焰檩。R本人自食惡果不足惜憔涉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望析苫。 院中可真熱鬧兜叨,春花似錦、人聲如沸衩侥。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茫死。三九已至跪但,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間峦萎,已是汗流浹背屡久。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留爱榔,地道東北人被环。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像详幽,于是被迫代替她去往敵國和親筛欢。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容