AIDL是什么
- Android Interface Definition Language,即Android接口定义语言。
- 使用aidl可以帮助我们发布以及调用远程服务,实现跨进程通信。
- 我们提供AIDL文件,可以方便系统为我们生成对应的接口(里面有Binder类)
为什么选择AIDL来分析Binder的工作机制。
Android开发中,Binder主要用在Service中,包含AIDL和Messenger,其中普通Service不涉及进程间通信,所以较为简单,无法触及Binder的核心,而Messenger的底层其实是AIDL,所以这里选择AIDL来分析Binder的工作机制。
在AS中新建AIDL示例
Book.java
首先新建一个实体类,该实体类实现Parcelable接口
1 | package com.feng.aidltest; |
Book.aidl
在同一个包中新建Book.aidl(由于AS不能同名,所以一开始随便起个名字,然后再refactor)
1 | // Book.aidl |
AS插件生成Parcelable的一个坑
默认生成的Parcelable对象只支持为 in 的定向 tag 。为什么呢?因为默认生成的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法——而这个方法其实并没有在 Parcelable 接口里面,所以需要我们从头写。
readFromParcel() 方法应当怎么写呢?,如图1
2
3
4
5public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
bookId = dest.readInt();
bookName = dest.readString();
}
其实Parcelable接口中并没有readFromParcel()这个方法,所以一般我们并不需要重写,但为了支持out和inout,我们只能自己写了。
IBookManager.aidl
在同一个包中新建IBookManager.aidl
1 | // IBookManager.aidl |
IBookManager.aidl是我们定义的一个接口,里面有两个示例方法。
注意:虽然在同一个包,但是在aidl中,还是要导入Book类
定向tag
AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。
IBookManager.java(系统为IBookManager.aidl生成的Binder类)
这个类在generated目录下(也可以在Packages模式下的包名下查看),如图
该方法代码有点多,这里先上个大概,具体方法实现先省略,后面会分析
1 | public interface IBookManager extends android.os.IInterface { |
上述代码是自动生成的,它继承了IInterface接口,同时它自己也是一个接口,所有可以在Binder中传输的接口都要继承IInterface接口。可以看到在最后,该接口声明了两个方法,这两个方法就是我们在IBookManager.aidl中的接口中声明的方法。
1 | public java.util.List<com.feng.aidltest.Book> getBookList() throws android.os.RemoteException; |
Stub(IBookManager接口的内部类)
现在分析一下IBookManager.java里的内部类Stub,这个就是一个Binder类。Stub类继承自Binder类,同时它实现了IBookManager接口(因为它是一个抽象类,所有它可以选择不重写或只重写一部分IBookManager和IInterface接口的方法,这里它重写了父类Binder的==onTransact方法==和IInterface接口的==asBinder方法==)。
当客户端和服务端都位于同一个进程时,方法调用不会走跨进程的transact过程,但当两者位于不同进程时,方法调用需要走transact过程,这个逻辑由Stub的内部代理类Proxy来完成。
asInterface
下面来看下Stub类中的方法,首先是asInterface方法。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15/**
* Cast an IBinder object into an com.feng.aidltest.IBookManager interface,
* generating a proxy if needed.
*/
public static com.feng.aidltest.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
//根据当前Binder的唯一标识查找对应的IInterface
if (((iin != null) && (iin instanceof com.feng.aidltest.IBookManager))) {
return ((com.feng.aidltest.IBookManager) iin);
}
return new com.feng.aidltest.IBookManager.Stub.Proxy(obj);
}
该方法用于将服务端的IBinder对象转换成客户端所需的AIDL接口类型的对象。这种转换是区分进程的,如果客户端和服务端位于同一进程中,那么此方法返回的是服务端的Stub对象本身,否则返回的是系统封装后的Stub.Proxy对象。
asBinder
此方法返回与此接口关联的Binder对象,是IInterface接口中的唯一方法。在Stub中,1
2
3
4
public android.os.IBinder asBinder() {
return this;
}
在Stub的内部代理类Proxy中,
1 | private android.os.IBinder mRemote; |
onTransact
1 |
|
这里重写了Binder的onTransact方法。该方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完后,就向reply中写入返回值(如果目标方法有返回值的话),这就是该方法的执行过程。
注意:如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性做权限验证,毕竟我们也不希望随便一个进程都能调用我们的远程服务。
Proxy(Stub的内部代理类)
Proxy是Stub的内部代理类,这是一个静态类,实现了IBookManager接口(由于不是抽象类,所以这里重写了IBookManager接口的所有方法:asBinder、getBookList和addBook)
getBookList
asBinder方法在Stub类中分析过了,所以这里先分析getBookList方法
1 |
|
该方法运行在客户端中,当客户端远程调用此方法时,具体的内部实现已经在上面代码的注释中写出。
addBook
addBook方法同样运行在客户端,它的执行过程和getBookList是一样的
1 |
|
注意
- 当客户端发起远程请求时,由于当前线程被挂起直至服务端返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求。
- 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应采用同步的方式去实现,因为它已经运行在一个线程中了。
参考
- 《Android 开发艺术探索》
- Android:学习AIDL,这一篇文章就够了(上)