震惊!Binder机制竟恐怖如斯!(上)
前言
花时间写博客真的不是浪费时间,第一次学习源码痛苦无比,勉强入了个门,过段时间回头再看,两眼一抹黑什么都不记得。痛定思痛,这次一定要沉下心来仔细整理。
Binder简介
Binder是Android中完成进程间通信的一种机制。
在Android系统中,每一个应用都运行在一个独立的虚拟机中,而每一个虚拟机都属于一个独立的系统进程。进程与进程之间是不会共享内存的,因此,应用与应用、应用与系统之间的通信就需要通过操作系统的某种机制来完成,在Linux中,Binder便是实现进程间通信的手段之一。
AIDL简介
本篇首先介绍应用与应用之间的Binder机制。
AIDL(Android Interface definition language)指安卓接口定义语言,是Android中IPC的一种实现方式,是对Binder的一种封装。其实现很像JAVA中的动态代理。
AIDL的简单实现分成以下几步
(一)创建一个aidl文件,在android studio中直接右键->new->aidl->aidl file即可。
interface IBinderTest {
void testVoidAidl();
String testStringAidl();
}
(二) 创建一个Service,使用一个内部类继承Stub,实现其中的方法;重写OnBind()方法,返回这个内部类的实例。
public class MyService extends Service {
private MyAidlTest binder;
public MyService() {
binder = new MyAidlTest();
}
@Override
public IBinder onBind(Intent intent) {
return binder;
}
private class MyAidlTest extends IBinderTest.Stub {
@Override
public void testVoidAidl() throws RemoteException {
}
@Override
public String testStringAidl() throws RemoteException {
return "test";
}
}
}
(三)将该aidl文件复制到要调用接口的项目中,其中包名也要和原项目相同。接着编译该项目。
(四)在新项目中连接远程服务,重写onServiceConnected方法,通过Stub的asInterface方法将IBinder对象转换成相应的aidl类,最后就能通过这个aidl类做爱做的事了。
Intent intent=new Intent(MainActivity.this,MyService.class);
Log.d(TAG, " : "+"bind");
bindService(intent, new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
Log.d(TAG, "onServiceConnected: "+"success");
IBinderTest binderTest=IBinderTest.Stub.asInterface(service);
try {
String result=binderTest.testStringAidl();
Log.d(TAG, "onServiceConnected: "+result);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
},0);
AIDL源码分析
代码结构
创建好一个aidl文件后重新编译项目,可以在build文件夹中获得一个与原文件名字相同的java文件,这就是系统自动为我们生成的核心代码。打开类结构图,可以看到IBinderTest这个类中包含一个Stub类,而Stub类中又包含一个Proxy类。
原理剧透
进程A与进程B进行通信,二者都含有一个相同的aidl文件。假设A要将消息发送给B,则A中的Proxy将消息发送到系统IBinder中,IBinder再将该消息发送到B中的Stub。即Proxy是发送方,Stub是接受方。其中IBinder就是内存中对AIDL的描述。下面是灵魂画师的作品:
源码分析
注册与初始化
在Stub的构造方法中有一个attachInterface方法,这是Binder类中的方法,它将当前的AIDL对象进行注册到系统中,用于之后判断是本地调用还是远程调用
public void attachInterface(IInterface owner, String descriptor) {
mOwner = owner;
mDescriptor = descriptor;
}
asInterface方法用于在客户端获取aidl对象。该方法获取IBinder对象后,将IBinder中的DESCRIPTOR与本地的DESCRIPTOR比较,若相同,则直接返回本地的AIDL对象;若不同,再通过Proxy创建一个AIDL对象。
public static IBinderTest asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof IBinderTest))) {
return ((IBinderTest) iin);
}
return new IBinderTest.Stub.Proxy(obj);
}
Proxy直接实现了AIDL接口,因此可以在其构造函数中直接创建我们所需要的AIDL对象
private static class Proxy implements IBinderTest
Proxy调用方法
在Proxy中,会自动实现我们在AIDL接口中定义的方法,方法的注释如下
@Override
public java.lang.String testStringAidl() throws android.os.RemoteException {
//从Parcel池(队列)中获取一个Parcel对象
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
java.lang.String _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
//远程调用该方法
mRemote.transact(Stub.TRANSACTION_testStringAidl, _data, _reply, 0);
//判断是否有异常
_reply.readException();
//同步获取返回结果
_result = _reply.readString();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
Stub回调方法
在Stub中,onTransact方法会回调具体继承了该Stub类的方法,即MyAidlTest中对AIDL接口的具体实现。
@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: {
...
}
case TRANSACTION_testVoidAidl: {
...
}
case TRANSACTION_testStringAidl: {
//检查合法性
data.enforceInterface(DESCRIPTOR);
//真正执行回调的地方
java.lang.String _result = this.testStringAidl();
//判断异常
reply.writeNoException();
//将结果
reply.writeString(_result);
return true;
}
}
return super.onTransact(code, data, reply, flags);
}
private class MyAidlTest extends IBinderTest.Stub {
@Override
public void testVoidAidl() throws RemoteException {
}
@Override
public String testStringAidl() throws RemoteException {
return "test";
}
}
Binder源码分析
文章写到现在,AIDL大致的流程已经清楚了,但是我们还会有一些疑问。比如Proxy的transact()
到底做了什么?Stub的onTransact
是怎么完成回调的?IBinder又是怎么注册到系统内存中的?
要回答这些问题,就要去分析Binder的源码了。Binder是IBinder的实现类,AIDL中的Stub都继承了Binder,而Proxy都继承了BinderProxy(BinderProxy是Binder的内部类)。
因此Proxy的transact()
实际上调用了BinderProxy的transact()
方法:
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
if (Binder.isTracingEnabled()) { Binder.getTransactionTracker().addTrace(); }
return transactNative(code, data, reply, flags);
}
在这段代码中,最终调用了transactNative
,这是一个本地方法,会通过NDK与底层通信,将应用层的内容写到底层操作系统的IBinder驱动中。与此同时,IBinder驱动会回调Binder中的onTransact
方法,将内容返回到应用层。
至于第三个问题,我们在创建AIDL对象时首先会创建Stub,而Stub又继承自Binder,所以我们会默认调用Binder的无参构造函数:
public Binder() {
init();
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Binder> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Binder class should be static or leaks might occur: " +
klass.getCanonicalName());
}
}
}
这个构造函数的第一行调用了一个叫init()
的方法:
private native final void init();
可见,这也是一个Native方法。显而易见,我们就是在这个方法中将IBinder注册到系统内存中的。
总结
最后叙述一下完整的流程,在之前的AIDL简介中,我们在客户端通过
binderTest=IBinderTest.Stub.asInterface(service);
这段代码,实际上是获取了一个客户端Proxy对象,通过该客户端Proxy调用AIDL接口方法时,会调用
mRemote.transact(Stub.TRANSACTION_testStringAidl, _data, _reply, 0);
那么再根据之前的灵魂画作,客户端Proxy会去系统中找到IBinder,IBinder再将请求回调给服务端的Stub,即回调了服务端Stub中的onTransact方法。
那么这个时候,服务端Stub就会调用
private class MyAidlTest extends IBinderTest.Stub
中具体实现的方法,并将结果写入 reply中,此时客户端proxy同步获取到这个reply。
整个流程结束!