本篇主要通过结合已经阅读的Binder机制相关资料(《Android开发艺术探索》和 http://weishu.me/2016/01/12/binder-index-for-newer/),通过AIDL来进行Binder机制的初步理解
感谢两位作者:任玉刚和WeiShu
当我们想对一个自定义数据类型(如:Book)来实现跨进程通信时,AIDL的实现核心包括四个部分,分别是:(1).Book.java (2).Book.aidl (3).IBookManager.aidl (4).IBookManager.java
以上4个文件是aidl不可缺少的部分,怎么实现就不说了,到处都有例子说明。这里就简单贴一下各个文件的代码:
/**
* Created by songjunmin on 2016/11/14.
* 支持跨进程传输的数据类型
*/
public class Book implements Parcelable {
public int bookId;
public String bookName;
//普通构造函数
public Book(int bookId, String bookName) {
this.bookId = bookId;
this.bookName = bookName;
}
//反序列化使用的构造函数
public Book(Parcel parcel) {
this.bookId = parcel.readInt();
this.bookName = parcel.readString();
}
//序列化
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel parcel, int i) {
parcel.writeInt(bookId);
parcel.writeString(bookName);
}
//反序列化
public static final Parcelable.Creator CREATOR = new Parcelable.Creator() {
@Override
public Book[] newArray(int i) {
return new Book[i];
}
@Override
public Book createFromParcel(Parcel parcel) {
return new Book(parcel);
}
};
@Override
public String toString() {
return "book id = " + bookId + "; book Name = " + bookName + "\n";
}
}
// Bookd.aidl
package com.example.songjunmin.aidldemo;
// Declare any non-default types here with import statements
parcelable Book;
// IBookManager.aidl
package com.example.songjunmin.aidldemo;
// Declare any non-default types here with import statements
import com.example.songjunmin.aidldemo.Book;
interface IBookManager {
List getBookList();
void addBook(in Book book);
}
/*
* This file is auto-generated. DO NOT MODIFY.
* Original file: /Users/songjunmin/GitHub/AIDLdemo/app/src/main/aidl/com/example/songjunmin/aidldemo/IBookManager.aidl
*/
package com.example.songjunmin.aidldemo;
public interface IBookManager extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.songjunmin.aidldemo.IBookManager
{
private static final java.lang.String DESCRIPTOR = "com.example.songjunmin.aidldemo.IBookManager";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.songjunmin.aidldemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.songjunmin.aidldemo.IBookManager asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.songjunmin.aidldemo.IBookManager))) {
return ((com.example.songjunmin.aidldemo.IBookManager)iin);
}
return new com.example.songjunmin.aidldemo.IBookManager.Stub.Proxy(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
{
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList:
{
data.enforceInterface(DESCRIPTOR);
java.util.List _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
case TRANSACTION_addBook:
{
data.enforceInterface(DESCRIPTOR);
com.example.songjunmin.aidldemo.Book _arg0;
if ((0!=data.readInt())) {
_arg0 = com.example.songjunmin.aidldemo.Book.CREATOR.createFromParcel(data);
}
else {
_arg0 = null;
}
this.addBook(_arg0);
reply.writeNoException();
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private static class Proxy implements com.example.songjunmin.aidldemo.IBookManager
{
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote)
{
mRemote = remote;
}
@Override public android.os.IBinder asBinder()
{
return mRemote;
}
public java.lang.String getInterfaceDescriptor()
{
return DESCRIPTOR;
}
@Override public java.util.List getBookList() throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.util.List _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.songjunmin.aidldemo.Book.CREATOR);
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override public void addBook(com.example.songjunmin.aidldemo.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);
if ((book!=null)) {
_data.writeInt(1);
book.writeToParcel(_data, 0);
}
else {
_data.writeInt(0);
}
mRemote.transact(Stub.TRANSACTION_addBook, _data, _reply, 0);
_reply.readException();
}
finally {
_reply.recycle();
_data.recycle();
}
}
}
static final int TRANSACTION_getBookList = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_addBook = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
}
public java.util.List getBookList() throws android.os.RemoteException;
public void addBook(com.example.songjunmin.aidldemo.Book book) throws android.os.RemoteException;
}
在上面的四个文件中,有两个java文件和两个aidl文件,Book.java是我们支持序列化跨进程传递的数据类,IBookManager.java是aidl工具为我们生成的最终跨进程接口,服务端和客户端通过这个接口来实现跨进程通信。
有了aidl文件之后,使用姿势如下:
/**
* Created by songjunmin on 2016/11/14.
* 建立服务端的Service,当来自别的进程需要进行IPC请求时,需要绑定到该Service获取Binder代理
*/
public class AIDLService extends Service {
private static final String TAG = "AIDLService";
//使用CopyOnWriteArrayList来支持线程同步,因为会有多个客户端访问服务端的场景
private CopyOnWriteArrayList mBookList = new CopyOnWriteArrayList<>();
private Binder mBinder = new IBookManager.Stub(){
//Stub是继承了Binder并且实现IBookManager接口的
//所以当进行实现方法的时候,会发现即有IBookManager里的接口方法,也有Binder里的覆写方法
//我们需要实现接口方法
@Override
public List getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1,"Android"));
mBookList.add(new Book(2,"iOS"));
}
}
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder binder) {
IBookManager bookManager = IBookManager.Stub.asInterface(binder);
try {
//客户端添加一本书
bookManager.addBook(new Book(3,"Python"));
List list = bookManager.getBookList();
for(int i = 0; i < list.size(); i++){
Book book = list.get(i);
Log.d(TAG, book.toString());
}
} catch (RemoteException e) {
e.printStackTrace();
}
Log.d(TAG,"绑定成功");
}
@Override
public void onServiceDisconnected(ComponentName name) {
Log.d(TAG, "解除绑定");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(MainActivity.this, AIDLService.class);
bindService(intent,conn, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
unbindService(conn);
super.onDestroy();
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.songjunmin.aidldemo">
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
intent-filter>
activity>
<service android:name=".AIDLService"
android:process=":remote"/>
application>
manifest>
在Manifest文件中,我们定义同一个app下有一个来自其他进程的服务,名为AIDLService,客户端通过bindService来绑定远程服务。
下面来进行跨进程通信的一步步分析。
在客户端,通过bindService来绑定服务,在bindService方法中需要建立一个ServiceConnection对象。 在服务端的回调方法onBind拿到一个IBinder对象,最后递交给客户端实现的ServiceConnection对象,在该对象的onServiceConnected(ComponentName name, IBinder binder)方法中完成对象的代理获取。
对这个过程进行拆分:
在AIDLService的onBind方法是这样的:
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
在onBind方法中返回了IBinder类型的mBinder对象,那么mBinder对象是怎么来的呢?在服务端创建了mBinder对象
private Binder mBinder = new IBookManager.Stub(){
//Stub是继承了Binder并且实现IBookManager接口的
//所以当进行实现方法的时候,会发现即有IBookManager里的接口方法,也有Binder里的覆写方法
//我们需要实现接口方法
@Override
public List getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
可以看到,在服务端,通过AIDL接口的内部类Stub,创建了Binder对象mBinder。
在aidl文件中可以知道内部类Stub是继承自Binder类的,而Binder又实现了IBinder接口,所以可以在onBind方法中返回。
具体看一下资料中对Binder和IBinder关系的说明:
从该说明中可以知道,凡是实现了IBinder接口的,就会受到底层驱动的支持(我可以理解为受到上帝的眷顾吗),从而可以跨进程传递这个数据。而Binder虽然实现了IBinder接口,但是其定义更加适合为本地的对象。
在该例子中,创建的mBinder是属于服务端自己的本地Binder对象,该对象可以通过跨进程传输协议(IBinder)来进行传递。
但是,但是,但是…(开始到第一个重要注意点了)
本地Binder通过跨进程协议(IBinder),传递到其他进程的并不是真身而是一个代理。
首先看客户端是如何实现ServiceConnection这个对象的:
public void onServiceConnected(ComponentName name, IBinder binder) {
IBookManager bookManager = IBookManager.Stub.asInterface(binder);
}
这个代码的理解就是:1.mBinder由于实现了IBinder接口,所以具体所谓的跨进程传递自己的能力;2.实际上,传递的是mBinder代理,而并不是mBinder真身
在IBookManager.Stub.asInterface方法中,完成了真身和代理的转变。看看asInterface的实现:
/**
* Cast an IBinder object into an com.example.songjunmin.aidldemo.IBookManager interface,
* generating a proxy if needed.
*/
public static com.example.songjunmin.aidldemo.IBookManager asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.example.songjunmin.aidldemo.IBookManager))) {
return ((com.example.songjunmin.aidldemo.IBookManager) iin);
}
return new com.example.songjunmin.aidldemo.IBookManager.Stub.Proxy(obj);
}
从这段代码中可以看出,asInterface方法接收了一个IBinder的接口对象,这个对象怎么来的呢?实际上是底层的Binder驱动提供的,在onBind方法中返回的mBinder实际上首先扔给了驱动,binder驱动进行了对mBinder包装改变(因为binder驱动也在一个进程中,onBind返回的过程由于首先给binder驱动,所以也属于进程间对象的传递,所以binder驱动肯定会对mBinder进行包装和改变),binder驱动完成了改变之后,又交给了客户端的进程,所以客户端进程中的onServiceConnected才可以拿到一个IBinder的对象,并执行后续的asInterface方法。以上就解释了asInterface方法的参数IBinder obj的由来,它并不是直接的mBinder(服务端的onBind方法所返回的)。
了解完参数之后,先不进入asInterface方法内部,再来看一下asInterface方法的返回值:是com.example.songjunmin.aidldemo.IBookManager
这个返回值类型就是我们的 AIDL接口,我们的所谓AIDL跨进程通信,实际上基于的是AIDL接口,在本例中,就是AIDL的IBookManager接口,所以我们需要返回AIDL接口 。
明确了返回值和方法参数之后,可以进入到asInterface方法内部了:
首先需要知道的是,这个方法是在客户端进程中调用的,所以首先执行本地AIDL接口查询,使用obj.queryLocalInterface(DESCRIPTOR)
由于在客户端进程,所以拿到binder驱动返回的IBinder对象obj之后执行本地查询,查询什么呢?查询DESCRIPTOR,即 :
private static final java.lang.String DESCRIPTOR = "com.example.songjunmin.aidldemo.IBookManager";
这里注意一点:一般如果客户端进程是来自别的应用A,而服务端进程也在一个应用B中,此时的跨进程同时是属于不同应用间的,所以在客户端使用上述方法的话,肯定也需要有与服务端同样的aidl文件.
正如《Android开发艺术探索》的P73页所说。不然客户端都没有DESCRIPTOR这个变量,怎么查询呢对吧.
查询完成之后判断本进程是否有该AIDL接口,若有,则执行:
if (((iin != null) && (iin instanceof com.example.songjunmin.aidldemo.IBookManager)))
若无,则通过内部类Stub的内部类Proxy来创建一个代理对象:
private static class Proxy implements com.example.songjunmin.aidldemo.IBookManager {
private android.os.IBinder mRemote;
Proxy(android.os.IBinder remote) {
mRemote = remote;
}
//...省略
}
代理对象内部有一个mRemote的IBinder对象,所以这个对象也是可以跨进程的(其实本质上都是客户端的Binder对象mBinder)。
在这个代理方法中,将binder驱动传递给客户端进程的IBinder赋值给了Proxy代理类的私有变量mRemote,而Proxy是Stub的内部类,这里千万不要被两个类在同一个文件中给弄迷糊了,这两个类在实际执行的时候是分属不同的进程空间中的。
Proxy这个内部类实现了AIDL接口,所以其对象也是可以供客户端进程使用的AIDL接口对象。
这样的话,就通过服务端的mBinder(源自AIDL接口的内部类Stub)、客户端的asInterface得到AIDL接口对象(基于代理)实现了跨进程AIDL接口的使用,进行数据交互。
客户端拿到的是服务端的代理对象,无法直接操作服务端的对象(为了安全起见),那么拿到代理对象也可以给客户端提供一种真实操作服务端对象的假象(比如,代理对象也向客户端提供了一个与真实服务端对象中相同的同名方法),但是这仅仅只是假象,名称相同,方法内部的执行逻辑在代理对象和真实服务端对象中是不一样的。
代理对象从客户端那拿到客户端请求的数据之后,进行包装,然后递交给底层的Binder驱动,驱动解析之后扔给真正的服务端对象,服务端再通过同名方法进行真实的逻辑处理。
@Override
public java.util.List<com.example.songjunmin.aidldemo.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.example.songjunmin.aidldemo.Book> _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
mRemote.transact(Stub.TRANSACTION_getBookList, _data, _reply, 0);
_reply.readException();
_result = _reply.createTypedArrayList(com.example.songjunmin.aidldemo.Book.CREATOR);
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
在这个代码块中,可以看到,代理对象压根没做什么真实在getBookList逻辑,其核心只是创建了一个包装客户端数据的_data,建立了一个可以返回给客户端的_reply,以及该方法的返回值数据_result。之后就通过mRemote.transact方法,指定了客户端调用的方法名称ID,以及两个数据包装对象,就一并扔给了Binder驱动了.........。此时,客户端线程会处于挂起状态,傻傻的等待服务端返回。
Binder驱动在收到代理递交过来的数据之后,经过敏锐的分析,发现原来这个是代理传递过来的,辣么代理的本意应该就是要告诉真正的服务端对象,告诉啥呢?要服务端对象执行getBookList方法,此时,bind驱动会找到真实的服务端对象(因为所有的跨进程服务端对象都会注册到Binder驱动中),并执行IBinder的ontransact方法,从而回调了真实服务端的ontransact方法:
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(DESCRIPTOR);
return true;
}
case TRANSACTION_getBookList: {
data.enforceInterface(DESCRIPTOR);
java.util.List<com.example.songjunmin.aidldemo.Book> _result = this.getBookList();
reply.writeNoException();
reply.writeTypedList(_result);
return true;
}
//省略
}
在该方法中,定位到getBookList方法的逻辑,发现调用了我们实际定义的接口方法getBookList,而该接口的具体实现就在服务端上层来实现的,需要开发人员自己定义:
private Binder mBinder = new IBookManager.Stub(){
//Stub是继承了Binder并且实现IBookManager接口的
//所以当进行实现方法的时候,会发现即有IBookManager里的接口方法,也有Binder里的覆写方法
//我们需要实现接口方法
@Override
public List getBookList() throws RemoteException {
return mBookList;
}
@Override
public void addBook(Book book) throws RemoteException {
mBookList.add(book);
}
};
这样子,就完成了服务端各个部分的意义了,客户端的各个部分的意义也明确了。
AIDL为我们生成的aidl接口名称和我们自定义的接口名称重名,很容易混淆我们的理解,这一点要明确。IBookManager.java是真正的AIDL接口,是用于跨进程实现的,内部的Stub实现了我们的IBookManager.aidl里声明的接口,这个是用于服务端上层自己实现的,AIDL中只是拿了个方法的壳而已。
所以,Binder机制的核心是“粘合剂”,当然AIDL只是粘合剂Binder所应用的一个场景,什么场景呢?跨进程通信,这里的通信实际上是调用两个进程都可以使用的接口方法,但是互相使用方法总要传递数据吧,又有代理的加入,所以才有了粘合剂Binder的必要。