用AIDL来分析Binder的工作机制

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
2
3
4
5
6
7
8
9
10
11
12
13
package com.feng.aidltest;

import android.os.Parcel;
import android.os.Parcelable;

public class Book implements Parcelable {
public int bookId;
public String bookName;

//构造方法

//一些 Parcelable的设置
}

Book.aidl

在同一个包中新建Book.aidl(由于AS不能同名,所以一开始随便起个名字,然后再refactor)

1
2
3
4
5
6
// Book.aidl
//注意:Book.aidl与Book.java的包名应当是一样的
package com.feng.aidltest;

// Declare any non-default types here with import statements
parcelable Book; //Book类在AIDL中的声明

AS插件生成Parcelable的一个坑

默认生成的Parcelable对象只支持为 in 的定向 tag 。为什么呢?因为默认生成的类里面只有 writeToParcel() 方法,而如果要支持为 out 或者 inout 的定向 tag 的话,还需要实现 readFromParcel() 方法——而这个方法其实并没有在 Parcelable 接口里面,所以需要我们从头写。

readFromParcel() 方法应当怎么写呢?,如图

1
2
3
4
5
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
bookId = dest.readInt();
bookName = dest.readString();
}

其实Parcelable接口中并没有readFromParcel()这个方法,所以一般我们并不需要重写,但为了支持out和inout,我们只能自己写了。

IBookManager.aidl

在同一个包中新建IBookManager.aidl

1
2
3
4
5
6
7
8
9
10
11
12
// IBookManager.aidl
package com.feng.aidltest;

import com.feng.aidltest.Book; //导入Book类

interface IBookManager {
//所有的返回值前都不需要加任何东西,不管是什么数据类型
List<Book> getBookList(); //从远程服务端获取图书列表

//传参时除了Java基本类型以及String,CharSequence之外的类型都需要在前面加上定向tag,具体加什么量需而定
void addBook(in Book book); //往图书列表添加一本书
}

IBookManager.aidl是我们定义的一个接口,里面有两个示例方法。
注意:虽然在同一个包,但是在aidl中,还是要导入Book类

定向tag

AIDL中的定向 tag 表示了在跨进程通信中数据的流向,其中 in 表示数据只能由客户端流向服务端, out 表示数据只能由服务端流向客户端,而 inout 则表示数据可在服务端与客户端之间双向流通。其中,数据流向是针对在客户端中的那个传入方法的对象而言的。in 为定向 tag 的话表现为服务端将会接收到一个那个对象的完整数据,但是客户端的那个对象不会因为服务端对传参的修改而发生变动;out 的话表现为服务端将会接收到那个对象的的空对象,但是在服务端对接收到的空对象有任何修改之后客户端将会同步变动;inout 为定向 tag 的情况下,服务端将会接收到客户端传来对象的完整信息,并且客户端将会同步服务端对该对象的任何变动。

IBookManager.java(系统为IBookManager.aidl生成的Binder类)

这个类在generated目录下(也可以在Packages模式下的包名下查看),如图

image

该方法代码有点多,这里先上个大概,具体方法实现先省略,后面会分析

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public interface IBookManager extends android.os.IInterface {
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.feng.aidltest.IBookManager {
private static final java.lang.String DESCRIPTOR = "com.feng.aidltest.IBookManager";
//Binder的唯一标识,一般用当前类名

public Stub() {
this.attachInterface(this, DESCRIPTOR); //关联当前Binder到IBookManager接口中
}

public static com.feng.aidltest.IBookManager asInterface(android.os.IBinder obj) {
//...
}

@Override
public android.os.IBinder asBinder() {
return this;
}

@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
//...
}

private static class Proxy implements com.feng.aidltest.IBookManager {

//...

@Override
public java.util.List<com.feng.aidltest.Book> getBookList() throws android.os.RemoteException {
//...
}


@Override
public void addBook(com.feng.aidltest.Book book) throws android.os.RemoteException {
//...
}
}

static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}

//该接口中定义的方法(跟IBookManager.aidl中接口的方法一致)
public java.util.List<com.feng.aidltest.Book> getBookList() throws android.os.RemoteException;
public void addBook(com.feng.aidltest.Book book) throws android.os.RemoteException;
}

上述代码是自动生成的,它继承了IInterface接口,同时它自己也是一个接口,所有可以在Binder中传输的接口都要继承IInterface接口。可以看到在最后,该接口声明了两个方法,这两个方法就是我们在IBookManager.aidl中的接口中声明的方法。

1
2
3
public java.util.List<com.feng.aidltest.Book> getBookList() throws android.os.RemoteException;

public void addBook(com.feng.aidltest.Book book) 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
@Override
public android.os.IBinder asBinder() {
return this;
}

在Stub的内部代理类Proxy中,

1
2
3
4
5
6
7
8
9
10
private android.os.IBinder mRemote;

Proxy(android.os.IBinder remote) {
mRemote = remote;
}

@Override
public android.os.IBinder asBinder() {
return mRemote;
}

onTransact

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {

//事务的接收方(服务端)
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}

//以下两个case是在Stub类中定义的
//分别执行服务端的方法getBookList、addBook,并通过reply返回相应的结果给客户端
case TRANSACTION_getBookList: {
data.enforceInterface(descriptor);
java.util.List<com.feng.aidltest.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook: {
data.enforceInterface(descriptor);
com.feng.aidltest.Book _arg0;
if ((0 != data.readInt())) {
_arg0 = com.feng.aidltest.Book.CREATOR.createFromParcel(data);
} else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}

//没有符合的case则交由父类Binder来处理
default: {
return super.onTransact(code, data, reply, flags);
}
}
}

这里重写了Binder的onTransact方法。该方法运行在服务端的Binder线程池中,当客户端发起跨进程请求时,远程请求会通过系统底层封装后交由此方法来处理。服务端通过code可以确定客户端所请求的目标方法是什么,接着从data中取出目标方法所需的参数(如果目标方法有参数的话),然后执行目标方法。当目标方法执行完后,就向reply中写入返回值(如果目标方法有返回值的话),这就是该方法的执行过程。

注意:如果此方法返回false,那么客户端的请求会失败,因此我们可以利用这个特性做权限验证,毕竟我们也不希望随便一个进程都能调用我们的远程服务。

Proxy(Stub的内部代理类)

Proxy是Stub的内部代理类,这是一个静态类,实现了IBookManager接口(由于不是抽象类,所以这里重写了IBookManager接口的所有方法:asBinder、getBookList和addBook)

getBookList

asBinder方法在Stub类中分析过了,所以这里先分析getBookList方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
public java.util.List<com.feng.aidltest.Book> getBookList() throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain(); //用于写入目标方法所需参数
android.os.Parcel _reply = android.os.Parcel.obtain(); //用于获取目标方法返回值
java.util.List<com.feng.aidltest.Book> _result; //返回值
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
//调用transact方法来发起RPC(Remote Procedure Call,远程过程调用)请求,同时当前线程挂起
//然后服务端的onTransact方法会被调用
//RPC过程返回后,当前线程才继续执行
_reply.readException(); //读取返回结果
_result = _reply.createTypedArrayList(com.feng.aidltest.Book.CREATOR);
//从_reply中取出RPC过程的返回结果
} finally {
_reply.recycle();
_data.recycle();
}
return _result; //返回从服务端获得的数据
}

该方法运行在客户端中,当客户端远程调用此方法时,具体的内部实现已经在上面代码的注释中写出。

addBook

addBook方法同样运行在客户端,它的执行过程和getBookList是一样的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public void addBook(com.feng.aidltest.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
//将Book对象(目标方法所需参数)写入_data中
if ((book != null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
} else {
_data.writeInt(0);
}
//发起RPC请求
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}

注意

  • 当客户端发起远程请求时,由于当前线程被挂起直至服务端返回数据,所以如果一个远程方法是耗时的,那么不能在UI线程中发起此远程请求。
  • 由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应采用同步的方式去实现,因为它已经运行在一个线程中了。

参考

-------------    本文到此结束  感谢您的阅读    -------------
0%