前言
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 需要保持包名類名一致赵辕。
如上圖,Student.java 包名為:com.fish.myapplication.service
再看Student.aidl 結(jié)構(gòu):
如上圖屋群,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é)果如下:
總結(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ù)流方向夺荒。
數(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é)果如下:
總結(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 線程池系列