Android IPC 之AIDL應(yīng)用(下)

前言

IPC 系列文章:
建議按順序閱讀。

Android IPC 之Service 還可以這么理解
Android IPC 之Binder基礎(chǔ)
Android IPC 之Binder應(yīng)用
Android IPC 之AIDL應(yīng)用(上)
Android IPC 之AIDL應(yīng)用(下)
Android IPC 之Messenger 原理及應(yīng)用
Android IPC 之服務(wù)端回調(diào)
Android IPC 之獲取服務(wù)(IBinder)
Android Binder 原理換個姿勢就頓悟了(圖文版)

上篇文章分析了AIDL原理及其基本使用叽躯,本篇文章將繼續(xù)深入分析AIDL其它用法及其注意事項骗污。
通過本篇文章,你將了解到:

1诅福、AIDL 傳遞非基本數(shù)據(jù)類型
2匾委、AIDL 數(shù)據(jù)流方向

1、AIDL 傳遞非基本數(shù)據(jù)類型

在上篇文章中定義AIDL文件時氓润,方法形參都是使用基本參數(shù)赂乐,實(shí)際需求里不僅僅只傳遞基本參數(shù)。比如客戶端想從服務(wù)端獲取學(xué)生信息咖气,包括姓名挨措、年齡等挖滤。

自定義數(shù)據(jù)類型

public class Student implements Parcelable {
    private String name;
    private int age;

    public Student(String name, int age) {
        this.name = name;
        this.age = age;
    }

    protected Student(Parcel in) {
        //從序列化里解析成員變量
        name = in.readString();
        age = in.readInt();
    }

    public static final Creator<Student> CREATOR = new Creator<Student>() {
        @Override
        public Student createFromParcel(Parcel in) {
            //構(gòu)造新對象
            return new Student(in);
        }

        @Override
        public Student[] newArray(int size) {
            return new Student[size];
        }
    };

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        //將成員變量寫入到序列化對象里
        dest.writeString(name);
        dest.writeInt(age);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

聲明了Student類,該類里有學(xué)生的信息:姓名运嗜、年齡壶辜。
跨進(jìn)程傳遞對象需要序列化數(shù)據(jù),因此采用Parcelable 進(jìn)行序列化担租,實(shí)現(xiàn)Parcelable需要實(shí)現(xiàn)其方法:describeContents()與writeToParcel(xx)砸民,并且還需要添加靜態(tài)類:CREATOR,用來反序列化數(shù)據(jù)奋救。
Parcelable序列化都是標(biāo)準(zhǔn)樣式岭参,實(shí)際上就做了兩件事:

1、將Student數(shù)據(jù)分別寫入到序列化對象Parcel里
2尝艘、從序列化對象Parcel里構(gòu)建出Student對象

AIDL 使用自定義數(shù)據(jù)類型

準(zhǔn)備好了數(shù)據(jù)類型演侯,接著來看看如何使用它。

package com.fish.myapplication;

import com.fish.myapplication.service.Student;//----------------(1)
interface IMyServer {
    void getStudentInfo(int age, in Student student);//------------(2)
}

(1)
與平時一致背亥,引入一個新的類型秒际,要將其類名import 出來。

(2)
getStudentInfo(xx)有個形參類型為:Student student狡汉,并且前邊還有個"in" 標(biāo)記(這個后續(xù)說)

自定義數(shù)據(jù)類型關(guān)聯(lián)的AIDL

上面的代碼是無法編譯通過的娄徊,還需要在AIDL里聲明自定義數(shù)據(jù)類型關(guān)聯(lián)的AIDL。
新建名為:Student 的AIDL 文件盾戴,默認(rèn)內(nèi)容如下:

package com.fish.myapplication.service;
interface Student {
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

將以上內(nèi)容刪除寄锐,改造成如下內(nèi)容:

package com.fish.myapplication.service;
parcelable Student;

這么改造后,Student.aidl生成的Student.java 文件內(nèi)容為空尖啡。
修改后橄仆,編譯成功桐筏。

注意事項

包名類名一致
Student.aidl和自定義數(shù)據(jù)類型Student.java 需要保持包名類名一致赵辕。

image.png

如上圖,Student.java 包名為:com.fish.myapplication.service

再看Student.aidl 結(jié)構(gòu):


image.png

如上圖屋群,Student.aidl 包名為:com.fish.myapplication.service

可以看出矛渴,兩者包名一致椎扬。

解決類重復(fù)問題
編寫過程中可能會遇到類重復(fù)問題:
先定義了Student.java,當(dāng)再定義Student.aidl 時具温,若兩者處于同一包下蚕涤,那么將無法創(chuàng)建Student.aidl文件。
分兩種方法解決:

第一種:先定義Student.aidl铣猩,并將其內(nèi)容改造揖铜,最后定義Student.java。
第二種:先定義Student.java 在與Student.aidl不同的包名下达皿,然后再定義Student.aidl天吓,并改造內(nèi)容贿肩,最后將Student.aidl 移動至與Student.java 同一包名下。

客戶端/服務(wù)端處理自定義數(shù)據(jù)類型

服務(wù)端業(yè)務(wù)

public class MyService extends Service {

    private final String TAG = "IPC";

    //構(gòu)造內(nèi)部類
    private IMyServer.Stub stub = new IMyServer.Stub() {
        @Override
        public void getStudentInfo(int age, Student student) throws RemoteException {
            Log.d(TAG, student.getName() + " in server");
        }
    };

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        //Stub 繼承自Binder龄寞,因此是IBinder類型
        return stub;
    }
}

獲取傳遞過來的Student汰规,并打印其姓名。

客戶端業(yè)務(wù)

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyServer iMyServer = IMyServer.Stub.asInterface(service);
            try {
                iMyServer.getStudentInfo(2, new Student("xiaoming", 18));
            } catch (Exception e) {

            }
        }

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

構(gòu)造Student對象物邑,并傳遞給服務(wù)端溜哮。
運(yùn)行結(jié)果如下:


image.png

總結(jié)AIDL 使用自定義數(shù)據(jù)類型步驟

1、構(gòu)造自定義數(shù)據(jù)類型同名.aidl文件
2色解、構(gòu)造自定義數(shù)據(jù)類型.java文件
3茂嗓、在AIDL 接口里使用自定義數(shù)據(jù)類型

2、AIDL 數(shù)據(jù)流方向

什么是數(shù)據(jù)流

回顧一下常用的方法調(diào)用方式:

    public void getStudentInfo(int age, Student student) {
        student.setName("modify");
    }

形參為:int 類型科阎;Student類型述吸;
在同一進(jìn)程里,當(dāng)調(diào)用該方法時锣笨,傳入Student引用蝌矛,方法里對Student成員變量進(jìn)行了更改,方法調(diào)用結(jié)束后错英,調(diào)用者持有的Student引用所指向的對象其內(nèi)容已經(jīng)更改了朴读。而對于int 類型,方法里卻無法更改走趋。
上述涉及到了經(jīng)典問題:傳值與傳址。

而對于不同的進(jìn)程噪伊,當(dāng)客戶端調(diào)用getStudentInfo(xx)方法時簿煌,雖然看起來是直接調(diào)用服務(wù)端的方法,實(shí)際上是底層中轉(zhuǎn)了數(shù)據(jù)鉴吹,因此當(dāng)初傳入Student姨伟,返回來的已經(jīng)不是同一個Student引用。
因此豆励,AIDL 規(guī)定了數(shù)據(jù)流方向夺荒。


image.png

數(shù)據(jù)流具體使用

從上圖可以看出,數(shù)據(jù)流方向有三種:

in
out
inout

為測試它們的差異良蒸,分別寫三個方法:

package com.fish.myapplication;

import com.fish.myapplication.service.Student;
interface IMyServer {
    void getStudentInfo(int age, in Student student);
    void getStudentInfo2(int age, out Student student);
    void getStudentInfo3(int age, inout Student student);
}

基本數(shù)據(jù)類型如 int技扼、String 默認(rèn)是數(shù)據(jù)流類型是: in,不用刻意標(biāo)注嫩痰。

服務(wù)端實(shí)現(xiàn)方法:

    private IMyServer.Stub stub = new IMyServer.Stub() {
        @Override
        public void getStudentInfoIn(int age, Student student) throws RemoteException {
            Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoIn");
            student.setName("change name getStudentInfoIn");
        }

        @Override
        public void getStudentInfoOut(int age, Student student) throws RemoteException {
            Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoOut");
            student.setName("change name getStudentInfoOut");
        }

        @Override
        public void getStudentInfoInout(int age, Student student) throws RemoteException {
            Log.d(TAG, "student name:" + student.getName() + " in server in getStudentInfoInout");
            student.setName("change name getStudentInfoInout");
        }
    };

將Student name 打印出來剿吻,并更改name 內(nèi)容。
客戶端調(diào)用服務(wù)端方法:

    ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IMyServer iMyServer = IMyServer.Stub.asInterface(service);
            try {
                Student student = new Student("xiaoming", 18);
                Log.d(TAG, "student name:" + student.getName() + " in client before getStudentInfoIn");
                iMyServer.getStudentInfoIn(2, student);
                Log.d(TAG, "student name:" + student.getName() + " in client after getStudentInfoIn");


                Student student2 = new Student("xiaoming", 18);
                Log.d(TAG, "student name:" + student2.getName() + " in client before getStudentInfoOut");
                iMyServer.getStudentInfoOut(2, student2);
                Log.d(TAG, "student name:" + student2.getName() + " in client after getStudentInfoOut");

                Student student3 = new Student("xiaoming", 18);
                Log.d(TAG, "student name:" + student3.getName() + " in client before getStudentInfoInout");
                iMyServer.getStudentInfoInout(2, student3);
                Log.d(TAG, "student name:" + student3.getName() + " in client after getStudentInfoInout");

            } catch (Exception e) {

            }
        }

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

構(gòu)造Student 對象串纺,并分別打印調(diào)用服務(wù)端方法前后Student name名字丽旅。
當(dāng)編譯的時候椰棘,發(fā)現(xiàn)編譯不過,還需要在Student.java 里添加方法:

    public Student() {}
    public void readFromParcel(Parcel parcel) {
        this.name = parcel.readString();
        this.age = parcel.readInt();
    }

運(yùn)行后結(jié)果如下:


image.png

總結(jié)一下規(guī)律:

1榄笙、使用 in 修飾Student邪狞,服務(wù)端收到Student的內(nèi)容,更改name后茅撞,客戶端收到Student帆卓,其name 并沒有改變。表示數(shù)據(jù)流只能從客戶端往服務(wù)端傳遞乡翅。
2鳞疲、使用 out 修飾Student,服務(wù)端并沒有收到Student的內(nèi)容蠕蚜,更改name后尚洽,客戶端收到Student,其name 已經(jīng)改變靶累。表示數(shù)據(jù)流只能從服務(wù)端往客戶端傳遞腺毫。
3、使用 inout 修飾Student挣柬,服務(wù)端收到Student的內(nèi)容潮酒,更改name后,客戶端收到Student邪蛔,其name 已經(jīng)改變急黎。表示數(shù)據(jù)流能在服務(wù)端和客戶端間傳遞。

數(shù)據(jù)流在代碼里的實(shí)現(xiàn)

AIDL 文件最終生成.java 文件侧到,因此在該文件里找答案勃教。
當(dāng)使用 in 修飾時:
對于客戶端,將Student數(shù)據(jù)寫入序列化對象匠抗。

          if ((student!=null)) {
            _data.writeInt(1);
            student.writeToParcel(_data, 0);
          }

對于服務(wù)端故源,并沒有將Student寫入回復(fù)的序列化對象。
當(dāng)使用 out 修飾時
對于客戶端汞贸,沒有將Student數(shù)據(jù)寫入序列化對象绳军。
對于服務(wù)端,將Student寫入回復(fù)的序列化對象矢腻。

          _arg1 = new com.fish.myapplication.service.Student();
          this.getStudentInfoOut(_arg0, _arg1);
          reply.writeNoException();
          if ((_arg1!=null)) {
            reply.writeInt(1);
            //寫入reply
            _arg1.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
          }

當(dāng)使用 inout 修飾時
實(shí)際上就是 in out 的結(jié)合门驾。

接下來將分析Messenger。

本文基于Android 10.0多柑。

您若喜歡猎唁,請點(diǎn)贊、關(guān)注,您的鼓勵是我前進(jìn)的動力

持續(xù)更新中诫隅,和我一起步步為營學(xué)習(xí)Android

1腐魂、Android各種Context的前世今生
2、Android DecorView 必知必會
3逐纬、Window/WindowManager 不可不知之事
4蛔屹、View Measure/Layout/Draw 真明白了
5、Android事件分發(fā)全套服務(wù)
6豁生、Android invalidate/postInvalidate/requestLayout 徹底厘清
7兔毒、Android Window 如何確定大小/onMeasure()多次執(zhí)行原因
8、Android事件驅(qū)動Handler-Message-Looper解析
9甸箱、Android 鍵盤一招搞定
10育叁、Android 各種坐標(biāo)徹底明了
11、Android Activity/Window/View 的background
12芍殖、Android Activity創(chuàng)建到View的顯示過
13豪嗽、Android IPC 系列
14、Android 存儲系列
15豌骏、Java 并發(fā)系列不再疑惑
16龟梦、Java 線程池系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窃躲,隨后出現(xiàn)的幾起案子计贰,更是在濱河造成了極大的恐慌,老刑警劉巖蒂窒,帶你破解...
    沈念sama閱讀 218,525評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件躁倒,死亡現(xiàn)場離奇詭異,居然都是意外死亡洒琢,警方通過查閱死者的電腦和手機(jī)樱溉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,203評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來纬凤,“玉大人,你說我怎么就攤上這事撩嚼⊥J浚” “怎么了?”我有些...
    開封第一講書人閱讀 164,862評論 0 354
  • 文/不壞的土叔 我叫張陵完丽,是天一觀的道長恋技。 經(jīng)常有香客問我,道長逻族,這世上最難降的妖魔是什么蜻底? 我笑而不...
    開封第一講書人閱讀 58,728評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮聘鳞,結(jié)果婚禮上薄辅,老公的妹妹穿的比我還像新娘要拂。我一直安慰自己,他們只是感情好站楚,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,743評論 6 392
  • 文/花漫 我一把揭開白布脱惰。 她就那樣靜靜地躺著,像睡著了一般窿春。 火紅的嫁衣襯著肌膚如雪拉一。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,590評論 1 305
  • 那天旧乞,我揣著相機(jī)與錄音蔚润,去河邊找鬼。 笑死尺栖,一個胖子當(dāng)著我的面吹牛嫡纠,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播决瞳,決...
    沈念sama閱讀 40,330評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼货徙,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了皮胡?” 一聲冷哼從身側(cè)響起痴颊,我...
    開封第一講書人閱讀 39,244評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎屡贺,沒想到半個月后蠢棱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,693評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡甩栈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,885評論 3 336
  • 正文 我和宋清朗相戀三年泻仙,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片量没。...
    茶點(diǎn)故事閱讀 40,001評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡玉转,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殴蹄,到底是詐尸還是另有隱情究抓,我是刑警寧澤,帶...
    沈念sama閱讀 35,723評論 5 346
  • 正文 年R本政府宣布袭灯,位于F島的核電站刺下,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏稽荧。R本人自食惡果不足惜橘茉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,343評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧畅卓,春花似錦擅腰、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,919評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至唐础,卻和暖如春箱歧,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背一膨。 一陣腳步聲響...
    開封第一講書人閱讀 33,042評論 1 270
  • 我被黑心中介騙來泰國打工呀邢, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人豹绪。 一個月前我還...
    沈念sama閱讀 48,191評論 3 370
  • 正文 我出身青樓价淌,卻偏偏與公主長得像,于是被迫代替她去往敵國和親瞒津。 傳聞我的和親對象是個殘疾皇子蝉衣,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,955評論 2 355