目录
- AIDL用法
服务端
客户端 - AIDL分析
2.1 AIDL文件结构
2.2 一些概念
2.3 AIDL的UML图
2.4 AIDL工作过程
2.5 AIDL注意事项
2.6 定向 tag:in、out、inout
2.7 一些想法
1. AIDL用法
- 下面是一个 Book 的存取例子,客户端与服务端分别为两个 App
- 服务端 Service 实现 addBook() 和 getBook() 的功能
- 客户端保存用户输入的 Book 的 name 和 price 属性值,并传送到服务端进程进行保存
服务端
第一:定义AIDL接口
- Book.java 只是一个实现了 parcelable 的普通 bean 类,有 name 和 price 两个属性
- BookManager.aidl 定义了两个接口方法,addBook() 和 getBook()
第二:实现服务端Service
public class BookService extends Service {
private List<Book> mBooks;
//定义本地Binder对象
private BookManager.Stub mBinder = new BookManager.Stub() {
@Override
public void addBook(Book book) throws RemoteException {
add(book);
}
@Override
public Book getBook(int index) throws RemoteException {
return get(index);
}
};
@Override
public void onCreate() {
super.onCreate();
mBooks = new ArrayList<>();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
//返回该Binder
return mBinder;
}
public void add(Book book) {
mBooks.add(book);
}
public Book get(int index) {
if (index < 0 || index >= mBooks.size()) {
return null;
}
return mBooks.get(index);
}
}
客户端
第一:将服务端 aidl 文件夹及其下所有文件原封不动复制一份到客户端代码下
第二:实现客户端Activity
public class MainActivity extends AppCompatActivity {
private EditText index;
private EditText name;
private EditText price;
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
//接收服务端返回的Binder
mBinder = BookManager.Stub.asInterface(service);
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
};
private BookManager mBinder;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
index = (EditText) findViewById(R.id.index);
name = (EditText) findViewById(R.id.name);
price = (EditText) findViewById(R.id.price);
}
public void register(View view) {
Intent intent = new Intent();
intent.setAction("com.qgstudio.server.BookService");
intent.setPackage("com.qgstudio.server");
bindService(intent, conn, BIND_AUTO_CREATE);
}
public void unregister(View view) {
unbindService(conn);
}
public void getBook(View view) {
try {
int i = Integer.parseInt(index.getText().toString());
Book book = mBinder.getBook(i);
name.setText(book.getName());
price.setText(book.getPrice()+"");
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
public void setBook(View view) {
try {
Book book = new Book();
String n = name.getText().toString();
int p = Integer.parseInt(price.getText().toString());
book.setName(n);
book.setPrice(p);
mBinder.addBook(book);
} catch (NumberFormatException e) {
e.printStackTrace();
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
2. AIDL分析
2.1 AIDL文件结构
这里就不看源码了,我们先来看看 AIDL 为我们生成的 Binder 实现类的文件结构
- 很简单,就是一个 Stub 类,还有一个内部类 Stub.Proxy 类
- 这里的 Stub 继承自 Binder 还实现了 BookManager 接口
- 而 Stub.Proxy 就只实现了 BookManager 接口,但它有一个成员变量 mRemote(是 BinderProxy )
2.2 一些概念
现在我们来捋一捋该文件的一些类的概念及类之间的关系:
- IBinder接口:代表了一种跨进程传输的能力,在跨进程数据流经驱动的时候,驱动会识别IBinder类型的数据
- IInterface接口:代表 Server 所提供的功能或者说是服务,在 Server 中定义的功能接口都需要扩展这个接口
- Stub:AIDL 中自动生成的类,继承自 Binder,同时实现了 IInterface 的接口,代表了可以即可以在进程间传递,同时又可以提供对应的服务的对象(角色:Binder 本地对象)
- Stub.Proxy:Stub 类的一个内部类,拥有一个 mRemote 成员变量,代表 BinderProxy。在 Stub.asInterface() 中首先会调用 IBinder.queryLocalInterface(DESCRIPTOR) 去查找本地Binder对象,如果没有说明系统传递进来的 IBinder 是一个 BinderProxy 对象(因为 Binder.queryLocalInterface() 返回本地 Binder 而 BInderProxy.queryLocalInterface() 直接返回 null)。此时会创建一个 Stub.Proxy 对象并且将它的 mRemote 初始化为该 BinderProxy,然后返回给调用者
2.3 AIDL的UML图
对应到之前 Binder 所讲的通信模型:
- Stub 就是服务端进程提供的 Binder 本地对象
- mRemote 就是在客户端的代理 BinderProxy
这里会发现还多了一个 Stub.Proxy 类:
- 当你查看 Stub.Proxy 类的 addBook() 和 getBook() 方法会发现它调用的其实就是 mRemote 的 transact(code,data,reply,flags) 方法
- mRemote 实际上是一个 BinderProxy, BinderProxy 是 Binder 的内部类,它并没有实现我们定义的 aidl 接口,但是定义了一个 transact() 方法,mRemote 用 code 这个值来区分到底调用的是 addBook() 还是 getBook()
- 对于用户来说是不想写这么麻烦的代码的,那最好的方式就是将这个 BinderProxy 再包装一层而且还要是实现 aidl 接口的,具体细节就留给 Stub.Proxy 去实现,用户不用管了
2.4 AIDL工作过程
连接过程
- Client 调用 bindService(),系统回调 Server 进程中 Service 的 onBind(),返回给 Client 进程一个对应的 BinderProxy, 这个是在 onServiceConnected() 回调方法中传递到 Client 进程的
- Client 调用 Stub.asInterface() 方法将该 BinderProxy 包装成我们定义的 BookManager 接口实现类
请求过程
- Client 将参数序列化到 _data 流中,通过 transact() 方法将它们传递给服务端,请求服务端调用指定方法,接着当前线程被挂起
- 系统回调 Server 的 onTransact() 方法从 _data 流中反序列化出参数,根据方法 ID 调用对应的本地方法,并将需要回传的数据序列化到 _reply 流,传回客户端
- Client 被唤醒,接收 _reply 数据流,并从中反序列出服务端传回来的结果,返回给函数调用者
2.5 AIDL注意事项
- 支持的数据类型
- 基本类型,包括 int,long,boolean,float,double,String
- Parcelable类型:所有实现了 Parcelable 接口的对象
- List:只支持 ArrayList,里面每个元素都必须被 AIDL 支持
- Map:只支持 HashMap,里面的每个元素都必须被 AIDL 支持,包括 key 和 value
- 其他 AIDL 生成的接口
- AIDL 文件中若使用到了除基本类型外的其他对象类型,必须新建一个和它同名的 AIDL 文件(且两者包名要相同),并在其中声明它为 Parcelable 类型
- AIDL 文件中使用到的自定义对象必须显示的 import 进来,即使 .java 文件和 .adil 文件就在同一个包下
- 文件摆放问题
- 建议是把所有和 AIDL 相关的类和文件,包括 .java 和 .aidl,全部放到同一个包中,这样就可以把整个包原封不动的复制到客户端中。为了能够使用 aidl 文件夹下的 .java 文件,需要在 app 的 build.gradle 下加上:
android {
//...
sourceSets {
main {
java.srcDirs += 'src/main/aidl'
}
}
}
- 定向 tag 问题
- AIDL 中除了基本类型,其他类型的参数必须标上定向 tag 的方向(一般选择 in 就行)
- AS 默认生成的 Parcelable 类对象只支持 in 的定向 tag。若要能够使用 out 或 inout,还要手动实现 readFromParcel() 方法(这个方法没有在 Parcelable 接口里):
//定义为 out 和 inout 的参数对象在服务端被改变后,
//客户端的该对象也会被改变,这与此方法相关
public void readFromParcel(Parcel dest) {
//注意,此处的读值顺序应当是和writeToParcel()方法中一致的
//对象在服务端的变化反作用与客户端
name = dest.readString();
price = dest.readInt();
}
客户端将对象传递给服务端,在服务端反序列化是调用了 CREATOR 的 createFromParcel() 方法,此方法中是先 new 了一个新对象,接着从反序列化流中读出属性数据再赋值给该新对象的属性,也就是说客户端传递的对象和服务端获得的对象并不是同一对象
客户端调用 AIDL 方法时当前线程会被挂起,若服务端执行比较耗时,会导致 ANR。此时应该开启另一个线程来调用这些方法
服务端的 AIDL 方法是运行在 Binder 线程池中的,本身可以执行耗时操作(不应该在里面再启动线程)并且需要处理线程同步问题
若客户端传给服务端的是一个 AIDL 类型的 Listener(或叫 Binder),则形成双向 IPC,服务端可回调客户端方法,但也是执行在客户端的 Binder 线程池中,也就是说客户端中被回调的方法若要更新 UI,需要使用 Handle 切换到主线程
2.6 定向 tag:in、out、inout
AIDL 中定向 tag 表示数据的流向,in 表示数据只能由客户端流向服务端,out 表示数据只能由服务端流向客户端,inout 表示数据能在两端之间传递。这里的数据指的是客户端在调用远程方法时传入的参数对象
- in:客户端只会将参数对象入到 _data流中,但不会从 _reply 流中读出该对象在服务端的改变结果,因此该对象在服务端的变化不会影响到客户端
- out:客户端不会将参数对象写到 _data 流中,在服务端会自己创建一个新的参数对象,并且会将对该对象的改变结果传回给客户端,客户端从 _reply 流中读出结果,并改变原参数对象
- inout:客户端会将参数对象写到 _data 流中,服务端接收该对象,并且会将对该对象的改变结果传回给客户端,客户端也会从 _reply 流中读出结果,并改变原参数对象
2.7 一些想法
- IPC 过程就是 Binder 驱动为 Client 和 Server 建立了两条通信通道 _data 和 _reply,双方将需要传递的参数序列化到 _data 流中,并且从 _reply 流中反序列化读取出结果
- 在 Java 层传递对象最方便,而底层是 C++ 写的,数据要流过底层只能采取序列化方式。在高层的通信上采用了代理模式,给用户造成了 Binder 对象能够在进程间传递的假象