前言
插件化技术火热已久,为什么会有插件化,时势造英雄吧,随着移动互联网的快速发展,业务的飞速增长,如何在有限时间给用户提供高质量的APP,当线上出现各种BUG,如何快速修复并发布上线,插件化的意义也就在这里了。目前插件化解决方案分为两个方向,一是以张勇的DroidPlugin框架为代表的动态替换方案,对Android底层的各种类进行Hook,来达到加载插件的四大组件的目的;二是以任玉刚的DL框架为代表的静态代理方案,通过ProxyActivity统一加载插件中的Activity。如何学好插件化这不是一件容易的事,需要我们对一些底层的知识有所了解,本篇文章讲解Binder和AIDL原理。
Binder原理
Binder是Android系统提供的一种IPC机制,从Android整体上看就是一个基于Binder通信的C/S架构,在Binder中分为Client端和Server端,哪个发消息哪个就是Client端,哪个接受消息哪个就是Server端。
通过上面Binder的架构图可以知道Client、Server和ServiceManager三者间关系,Server进程注册的各种Service会保存在ServiceManager中,对于ServiceManager来说,Server是ServiceManager的客户端,那么ServiceManager就是服务端了。如果Client进程想要使用某个Service,需要到ServiceManager中获取,对ServiceManager来说,Client是ServiceManager的客户端。Client与Server通过ServiceManager建立了通信的道路,上面的IPC就是进程间通信,Client与Server分属两个不同的进程,这两个进程如果想要通信就需要ServiceManager来配合。
接着看下图的Binder通信流程图:
Client端想要调用Server端的方法,由于Client和Server在两个不同的进程中,因此不能直接访问,这时需要Server端向ServiceManager注册服务,注册的过程中会向Binder驱动的全局链表binder_procs中插入服务端的信息,ServiceManager的svcinfo列表用于缓存注册过的服务,Client会通过ServiceManager的svcinfo列表查询并返回服务端的代理,拿到服务端代理对象就可以调用代理对象的方法,调用过程中通过BinderProxy将请求参数发送给ServiceManager,通过共享内存的方式完成通信,Binder的整体通信流程已经讲了大概,下面稍微补充下共享内存的知识。
跨进程通信是需要内核空间支持的,在Android系统中,这个运行在内核空间,负责各个用户进程通过Binder实现通信的内核模块就叫Binder驱动。Binder IPC 机制中涉及到的内存映射通过 mmap() 来实现,mmap() 是操作系统中一种内存映射的方法。内存映射简单的讲就是将用户空间的一块内存区域映射到内核空间。映射关系建立后,用户对这块内存区域的修改可以直接反应到内核空间;反之内核空间对这段区域的修改也能直接反应到用户空间。
内存映射能减少数据拷贝次数,实现用户空间和内核空间的高效互动。两个空间各自的修改能直接反映在映射的内存区域,从而被对方空间及时感知。也正因为如此,内存映射能够提供对进程间通信的支持。
Binder IPC通信过程是这样的,首先Binder驱动在内核空间创建一个数据接收缓存区,接着在内核空间开辟一块内核缓存区,建立内核缓存区和内核中数据接收缓存区以及内核中数据接收缓存区和接受进程用户空间地址之间的映射关系。发送方进程通过系统调用 copy_from_user() 将数据 copy 到内核中的内核缓存区,由于内核缓存区和接收进程的用户空间存在内存映射,因此也就相当于把数据发送到了接收进程的用户空间,这样便完成了一次进程间的通信。
AIDL原理
首页创建一个Test.aidl,里面一个add方法,生成的代码如下:
public interface Test extends android.os.IInterface
{
/** Local-side IPC implementation stub class. */
public static abstract class Stub extends android.os.Binder implements com.example.administrator.project01.Test
{
private static final java.lang.String DESCRIPTOR = "com.example.administrator.project01.Test";
/** Construct the stub at attach it to the interface. */
public Stub()
{
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.example.administrator.project01.Test interface,
* generating a proxy if needed.
*/
public static com.example.administrator.project01.Test asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.administrator.project01.Test))) {
return ((com.example.administrator.project01.Test)iin);
}
return new com.example.administrator.project01.Test.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
{
java.lang.String descriptor = DESCRIPTOR;
switch (code)
{
case INTERFACE_TRANSACTION:
{
reply.writeString(descriptor);
return true;
}
case TRANSACTION_add:
{
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
int _result = this.add(_arg0, _arg1);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
default:
{
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.example.administrator.project01.Test
{
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 int add(int first, int second) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
public int add(int first, int second) throws android.os.RemoteException;
}
IBinder 是一个接口,代表了一种跨进程通信的能力。只要实现了这个接口,这个对象就能跨进程传输。IInterface 代表的就是 Server 进程对象具备什么样的能力(能提供哪些方法,其实对应的就是 AIDL 文件中定义的接口),BinderProxy 类是 Binder 类的一个内部类,它代表远程进程的 Binder 对象的本地代理;这两个类都继承自 IBinder, 因而都具有跨进程传输的能力;实际上,在跨进程的时候,Binder 驱动会自动完成这两个对象的转换。Stub继承了 Binder, 说明它是一个 Binder 本地对象,它实现了 IInterface 接口,表明它具有 Server 承诺给 Client 的能力;Stub 是一个抽象类,具体的 IInterface 的相关实现需要开发者自己实现。
public static com.example.administrator.project01.Test asInterface(android.os.IBinder obj)
{
if ((obj==null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin!=null)&&(iin instanceof com.example.administrator.project01.Test))) {
return ((com.example.administrator.project01.Test)iin);
}
return new com.example.administrator.project01.Test.Stub.Proxy(obj);
}
在Stub中一个asInterface方法,它的用处就是判断Client和Server是否在同一个进程中,如果在同一个进程中就是直接返回本地对象,否则返回代理对象Proxy。
private static class Proxy implements com.example.administrator.project01.Test
{
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 int add(int first, int second) throws android.os.RemoteException
{
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(first);
_data.writeInt(second);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();
_result = _reply.readInt();
}
finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
}
static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
}
代理类Proxy实现了Test接口,add方法中,通过Parcel将数据序列化,_data用于装载数据,将函数名称和函数参数写入到_data中,_reply用于接收函数返回值,最后使用IBinder的transact方法将数据传递给Binder的Server端了,mRemote就是通过asInterface方法传递过来的obj参数,在Server端接收到Client进程传递过来的数据,调用对应的函数,得到结果并返回。