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 原理換個(gè)姿勢就頓悟了(圖文版)

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

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

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

在上篇文章中定義AIDL文件時(shí),方法形參都是使用基本參數(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)造新對(duì)象
            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) {
        //將成員變量寫入到序列化對(duì)象里
        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)程傳遞對(duì)象需要序列化數(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ù)分別寫入到序列化對(duì)象Parcel里
2、從序列化對(duì)象Parcel里構(gòu)建出Student對(duì)象

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)
與平時(shí)一致,引入一個(gè)新的類型,要將其類名import 出來。

(2)
getStudentInfo(xx)有個(gè)形參類型為:Student student,并且前邊還有個(gè)"in" 標(biāo)記(這個(gè)后續(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)容為空。
修改后,編譯成功。

注意事項(xiàng)

包名類名一致
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ù)問題
編寫過程中可能會(huì)遇到類重復(fù)問題:
先定義了Student.java,當(dāng)再定義Student.aidl 時(shí),若兩者處于同一包下,那么將無法創(chuàng)建Student.aidl文件。
分兩種方法解決:

第一種:先定義Student.aidl,并將其內(nèi)容改造,最后定義Student.java。
第二種:先定義Student.java 在與Student.aidl不同的包名下,然后再定義Student.aidl,并改造內(nèi)容,最后將Student.aidl 移動(dòng)至與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對(duì)象,并傳遞給服務(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)用該方法時(shí),傳入Student引用,方法里對(duì)Student成員變量進(jìn)行了更改,方法調(diào)用結(jié)束后,調(diào)用者持有的Student引用所指向的對(duì)象其內(nèi)容已經(jīng)更改了。而對(duì)于int 類型,方法里卻無法更改。
上述涉及到了經(jīng)典問題:傳值與傳址。

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


image.png

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

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

in
out
inout

為測試它們的差異,分別寫三個(gè)方法:

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 對(duì)象,并分別打印調(diào)用服務(wù)端方法前后Student name名字。
當(dāng)編譯的時(shí)候,發(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 修飾時(shí):
對(duì)于客戶端,將Student數(shù)據(jù)寫入序列化對(duì)象。

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

對(duì)于服務(wù)端,并沒有將Student寫入回復(fù)的序列化對(duì)象。
當(dāng)使用 out 修飾時(shí)
對(duì)于客戶端,沒有將Student數(shù)據(jù)寫入序列化對(duì)象。
對(duì)于服務(wù)端,將Student寫入回復(fù)的序列化對(duì)象。

          _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í)
實(shí)際上就是 in out 的結(jié)合。

接下來將分析Messenger。

本文基于Android 10.0。

您若喜歡,請(qǐng)點(diǎn)贊、關(guān)注,您的鼓勵(lì)是我前進(jìn)的動(dòng)力

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

1、Android各種Context的前世今生
2、Android DecorView 必知必會(huì)
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ū)動(dòng)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 存儲(chǔ)系列
15、Java 并發(fā)系列不再疑惑
16、Java 線程池系列

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。