前言
IPC 系列文章:
建议按顺序阅读。
Android IPC 之Service 还可以这么理解
Android IPC 之Binder基础
Android IPC 之Binder应用
Android IPC 之AIDL应用(上)
Android IPC 之AIDL应用(下)
Android IPC 之Messenger 原理及应用
Android IPC 之获取服务(IBinder)
上篇文章分析了AIDL原理及其基本使用,本篇文章将继续深入分析AIDL其它用法及其注意事项。
通过本篇文章,你将了解到:
1、AIDL 传递非基本数据类型
2、AIDL 数据流方向
1、AIDL 传递非基本数据类型
在上篇文章中定义AIDL文件时,方法形参都是使用基本参数,实际需求里不仅仅只传递基本参数。比如客户端想从服务端获取学生信息,包括姓名、年龄等。
自定义数据类型
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 CREATOR = new Creator() {
@Override
public Student createFromParcel(Parcel in) {
//构造新对象
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类,该类里有学生的信息:姓名、年龄。
跨进程传递对象需要序列化数据,因此采用Parcelable 进行序列化,实现Parcelable需要实现其方法:describeContents()与writeToParcel(xx),并且还需要添加静态类:CREATOR,用来反序列化数据。
Parcelable序列化都是标准样式,实际上就做了两件事:
1、将Student数据分别写入到序列化对象Parcel里
2、从序列化对象Parcel里构建出Student对象
AIDL 使用自定义数据类型
准备好了数据类型,接着来看看如何使用它。
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" 标记(这个后续说)
自定义数据类型关联的AIDL
上面的代码是无法编译通过的,还需要在AIDL里声明自定义数据类型关联的AIDL。
新建名为:Student 的AIDL 文件,默认内容如下:
package com.fish.myapplication.service;
interface Student {
void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
double aDouble, String aString);
}
将以上内容删除,改造成如下内容:
package com.fish.myapplication.service;
parcelable Student;
这么改造后,Student.aidl生成的Student.java 文件内容为空。
修改后,编译成功。
注意事项
包名类名一致
Student.aidl和自定义数据类型Student.java 需要保持包名类名一致。
如上图,Student.java 包名为:com.fish.myapplication.service
再看Student.aidl 结构:
如上图,Student.aidl 包名为:com.fish.myapplication.service
可以看出,两者包名一致。
解决类重复问题
编写过程中可能会遇到类重复问题:
先定义了Student.java,当再定义Student.aidl 时,若两者处于同一包下,那么将无法创建Student.aidl文件。
分两种方法解决:
第一种:先定义Student.aidl,并将其内容改造,最后定义Student.java。
第二种:先定义Student.java 在与Student.aidl不同的包名下,然后再定义Student.aidl,并改造内容,最后将Student.aidl 移动至与Student.java 同一包名下。
客户端/服务端处理自定义数据类型
服务端业务
public class MyService extends Service {
private final String TAG = "IPC";
//构造内部类
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,并打印其姓名。
客户端业务
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) {
}
};
构造Student对象,并传递给服务端。
运行结果如下:
总结AIDL 使用自定义数据类型步骤
1、构造自定义数据类型同名.aidl文件
2、构造自定义数据类型.java文件
3、在AIDL 接口里使用自定义数据类型
2、AIDL 数据流方向
什么是数据流
回顾一下常用的方法调用方式:
public void getStudentInfo(int age, Student student) {
student.setName("modify");
}
形参为:int 类型;Student类型;
在同一进程里,当调用该方法时,传入Student引用,方法里对Student成员变量进行了更改,方法调用结束后,调用者持有的Student引用所指向的对象其内容已经更改了。而对于int 类型,方法里却无法更改。
上述涉及到了经典问题:传值与传址。
而对于不同的进程,当客户端调用getStudentInfo(xx)方法时,虽然看起来是直接调用服务端的方法,实际上是底层中转了数据,因此当初传入Student,返回来的已经不是同一个Student引用。
因此,AIDL 规定了数据流方向。
数据流具体使用
从上图可以看出,数据流方向有三种:
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);
}
基本数据类型如 int、String 默认是数据流类型是: in,不用刻意标注。
服务端实现方法:
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 内容。
客户端调用服务端方法:
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) {
}
};
构造Student 对象,并分别打印调用服务端方法前后Student name名字。
当编译的时候,发现编译不过,还需要在Student.java 里添加方法:
public Student() {}
public void readFromParcel(Parcel parcel) {
this.name = parcel.readString();
this.age = parcel.readInt();
}
运行后结果如下:
总结一下规律:
1、使用 in 修饰Student,服务端收到Student的内容,更改name后,客户端收到Student,其name 并没有改变。表示数据流只能从客户端往服务端传递。
2、使用 out 修饰Student,服务端并没有收到Student的内容,更改name后,客户端收到Student,其name 已经改变。表示数据流只能从服务端往客户端传递。
3、使用 inout 修饰Student,服务端收到Student的内容,更改name后,客户端收到Student,其name 已经改变。表示数据流能在服务端和客户端间传递。
数据流在代码里的实现
AIDL 文件最终生成.java 文件,因此在该文件里找答案。
当使用 in 修饰时:
对于客户端,将Student数据写入序列化对象。
if ((student!=null)) {
_data.writeInt(1);
student.writeToParcel(_data, 0);
}
对于服务端,并没有将Student写入回复的序列化对象。
当使用 out 修饰时
对于客户端,没有将Student数据写入序列化对象。
对于服务端,将Student写入回复的序列化对象。
_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);
}
当使用 inout 修饰时
实际上就是 in out 的结合。
接下来将分析Messenger。
本文基于Android 10.0。
您若喜欢,请点赞、关注,您的鼓励是我前进的动力
持续更新中,和我一起步步为营学习Android
1、Android各种Context的前世今生
2、Android DecorView 一窥全貌(上)
3、Android DecorView 一窥全貌(下)
4、Window/WindowManager 不可不知之事
5、View Measure/Layout/Draw 真明白了
6、Android事件分发全套服务
7、Android invalidate/postInvalidate/requestLayout 彻底厘清
8、Android Window 如何确定大小/onMeasure()多次执行原因
9、Android事件驱动Handler-Message-Looper解析
10、Android 键盘一招搞定
11、Android 各种坐标彻底明了
12、Android Activity/Window/View 的background
13、Android IPC 之Service 还可以这么理解
14、Android IPC 之Binder基础
15、Android IPC 之Binder应用
16、Android IPC 之AIDL应用(上)