季春初始,天气返暖,新冠渐去,正值学习好时机。在Android系统中,AIDL一直在Framework和应用层上扮演着很重要的角色,今日且将其原理简单分析。(文2020.03.30)
Android系统中对原理的分析基本离不开对源码的阅读,我理解的原理分析:
原理分析 = 基本概念 + 源码分析 + 实践
正如创始人Linus Torvalds的名言:RTFSC(read the f**king source code)。本文也是按照上述结构来介绍AIDL的。
接下来先简单提一提IPC、序列化和Parcel三个概念。
A. 线程是CPU调度的最小单元,同时线程是一种有限的系统资源。
B. 进程一般指一个执行单元,在PC和移动设备上指一个程序或一个应用
C. 一个进程可以包含多个线程,包含与被包含的关系。
D. Java默认只有一个线程,叫主线程,在android中也叫做UI线程
A. 定义:IPC(inter-process Commnication)跨进程的通信,多进程之间的通信。
B. 为什么需要进程通信:我们知道Android一般情况下一个应用是默认运行在一个进程中,但有可能一个应用中需要采用多进程模式(process属性)来实现(比如获取多份内存空间),或者两个应用之间需要获取彼此之间的数据,还有AMS(系统服务)对每个应用中四大组件的管理,系统服务是运行在一个单独的进程中,这些统统需要IPC。
Android IPC方式:文件共享、ContentProvider(底层是Binder实现)、Socket、Binder(AIDL、Messenger)。
序列化是指将一个对象转化为二进制或者是某种格式的字节流,将其转换为易于保存或网络传输的格式的过程,反序列化则是将这些字节重建为一个对象的过程。Serializable和Parcelable接口可以完成对象的序列化。
Serializable是Java提供的序列化接口,使用时只需要实现Serializable接口并声明一个serialVersionUID(用于反序列化)
A. writeToParcel:将对象序列化为一个Parcel对象,将类的数据写入外部提供的Parcel中
B. describeContents:内容接口描述,默认返回0
C. 实例化静态内部对象CREATOR实现接口Parcelable.Creator,需创建一个数组(newArray(int size)) 供外部类反序列化本类数组使用;createFromParcel创建对象
D. readFromParcel:从流里读取对象,写的顺序和读的顺序必须一致。
Serializable使用简单,但是开销很大(大量I/O操作),Parcelable是Android中的序列化方式,使用起来麻烦,但是效率很高,是Android推荐的方式。Parcelable主要用在内存序列化上,如果要将对象序列化到存储设备或者通过网络传输也是可以的,但是会比较复杂,这两种情况建议使用Serializable。
Parcel主要就是用来进行IPC通信,是一种容器,他可以包含数据或者是对象引用,并且能够用于Binder的传输。同时支持序列化以及跨进程之后进行反序列化,同时其提供了很多方法帮助开发者完成这些功能。Parcel的读写都是一个指针操作的,有writeInt(int val)、writeString(String val)、setDataPosition(int val)、readInt()、readString()、recycle()方法。
AIDL:Android interface definition Language,Android 接口定义语言。使用aidl可以发布以及调用远程服务,实现跨进程通信。将服务的aidl放到对应的src目录,工程的gen目录会生成相应的接口类。
AIDL的语法十分简单,与Java语言基本保持一致,主要规则有以下几点:
1)AIDL文件以 .aidl 为后缀名
2)AIDL支持的数据类型分为如下几种:
A. 八种基本数据类型:byte、char、short、int、long、float、double、boolean
String,CharSequence
B. 实现了Parcelable接口的数据类型
C. List 类型。List承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
D. Map类型。Map承载的数据必须是AIDL支持的类型,或者是其它声明的AIDL对象
3)AIDL文件可以分为两类
A. 一类用来声明实现了Parcelable接口的数据类型,以供其他AIDL文件使用那些非默认支持的数据类型。
B. 另一类是用来定义接口方法,声明要暴露哪些接口给客户端调用,定向Tag就是用来标注这些方法的参数值。
4)定向Tag
定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值。
A. in 表示数据只能由客户端流向服务端
B. out 表示数据只能由服务端流向客户端
C. inout 则表示数据可在服务端与客户端之间双向流通。
如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。
5)明确导包
在AIDL文件中需要明确标明引用到的数据类型所在的包名,如java的import导入。
View Code
1 // UserControl.aidl 2 package com.haybl.aidl_test; 3 4 import com.haybl.aidl_test.User; 5 6 // Declare any non-default types here with import statements 7 8 interface UserController { 9 10 ListgetUserList(); 11 12 void addUserInOut(inout User user); 13 }
复制上述两个AIDL文件至服务端代码。创建 Service,在其中创建上面生成的 Binder 对象实例,实现接口定义的方法在 onBind() 中返回。
1 @Override 2 public IBinder onBind(Intent intent) { 3 Log.d(TAG, "onBind"); 4 return stub; 5 } 6 7 private UserController.Stub stub = new UserController.Stub() { 8 @Override 9 public ListgetUserList() throws RemoteException { 10 Log.d(TAG, "getUserList"); 11 return mUserList; 12 } 13 14 @Override 15 public void addUserInOut(User user) throws RemoteException { 16 if (user != null) { 17 Log.d(TAG, "Server receive a new user by InOut = " + user.getName()); 18 // 服务端改变数据,通过InOut Tag会同步到客户端。数据是双向流动的 19 user.setName("I'm changed"); 20 mUserList.add(user); 21 } else { 22 Log.d(TAG, "Server receive a null by InOut"); 23 } 24 } 25 };
世卫官员:新冠疫苗问世前 生活方式有重大改变