前言
Binder驱动有很多小的细节,目的就是提升Binder通信的效率。比较典型的是两个机制,因为没有官方名词,我对这两种机制起个名字:"线程栈复用"和"远程转本地"。前者是为了减少线程消耗,后者是为了减少跨进程次数。这篇文章就是介绍"线程栈复用",以后我们再讲"远程转本地"。
一、假设一个场景
进程A在UI线程发起一次Binder通信到进程B的服务B,在服务B中再次发起Binder通信到进程A的服务A,请问整个过程会牵涉到几个线程,按照常理理解应该有三个线程:
1.进程A UI线程 2.进程B Binde线程 3.进程A Binder线程 第一次Binder通信:进程A UI线程——>进程B Binde线程 第二次Binder通信:进程B Binder线程——>进程A Binder线程。
二、写个Demo
那事实上真的是会用到三个线程吗?我们写的Demo验证一下
2.1 进程A
定义一个AIDL
代码语言:javascript复制interface IServiceA {
void sendMsg(String msg);
}
关键代码
代码语言:javascript复制public class MainActivity extends AppCompatActivity {
private ServiceA mServiceA = new ServiceA();
public class ServiceA extends IServiceA.Stub {
@Override
public void sendMsg(String msg) throws RemoteException {
Log.v("KobeWang", "get msg : " msg, new Exception("KobeWang"));
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//获得Service B的服务
Intent intent = new Intent(this, ServerB.class);
this.bindService(intent, new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
IServiceB serviceB = IServiceB.Stub.asInterface(service);
try {
//给ServiceB发送msg,并将ServiceA发给ServiceB
Log.v("KobeWang", "send msg start: " "hello ServiceB");
serviceB.sendMsg(mServiceA, "hello ServiceB");
Log.v("KobeWang", "send msg end: " "hello ServiceB");
} catch (Exception e) {
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
}
}, Context.BIND_AUTO_CREATE);
}
}
2.2 进程B
定义一个AIDL
代码语言:javascript复制interface IServiceB {
void sendMsg(IBinder binder, String msg);
}
关键代码
代码语言:javascript复制public class ServerB extends Service {
@Override
public IBinder onBind(Intent intent) {
return new ServiceB();
}
public class ServiceB extends IServiceB.Stub {
@Override
public void sendMsg(IBinder binder, String msg) throws RemoteException {
Log.v("KobeWang", "get msg : " msg);
if (binder != null) {
IServiceA serviceA = IServiceA.Stub.asInterface(binder);
Log.v("KobeWang", "send msg start: " "hello ServiceA");
serviceA.sendMsg("hello ServiceB");
Log.v("KobeWang", "send msg end: " "hello ServiceA");
}
}
}
}
由于demo写一个module中,别忘了将ServceB运行在另外一个进程,否则就会触发另一个机制:"远程转本地"
代码语言:javascript复制<service android:name=".ServerB"
android:exported="true"
android:process=":server">
</service>
三、运行结果
代码语言:javascript复制//运行在进程A的UI线程,开始ServiceB的Binder通信,休眠等返回
02-29 14:33:26.559 27948 27948 V KobeWang: send msg start: hello ServiceB
//运行在进程B的Binder线程
02-29 14:33:26.560 28006 28029 V KobeWang: get msg : hello ServiceB
//运行在进程B的Binder线程
02-29 14:33:26.561 28006 28029 V KobeWang: send msg start: hello ServiceA
//运行在进程A的UI线程
02-29 14:33:26.565 27948 27948 V KobeWang: get msg : hello ServiceA
//运行在进程B的Binder线程
02-29 14:33:26.566 28006 28029 V KobeWang: send msg end: hello ServiceA
//运行在进程A的UI线程,ServiceB的Binder通信结束
02-29 14:33:26.566 27948 27948 V KobeWang: send msg end: hello ServiceB
看到结果,我们简化一下
代码语言:javascript复制//运行在进程A的UI线程,开始ServiceB的Binder通信,休眠等返回
02-29 14:33:26.559 27948 27948 V KobeWang: send msg start: hello ServiceB
//运行在进程A的UI线程,响应ServiceA
02-29 14:33:26.565 27948 27948 V KobeWang: get msg : hello ServiceA
//运行在进程A的UI线程,ServiceB的Binder通信结束
02-29 14:33:26.566 27948 27948 V KobeWang: send msg end: hello ServiceB
我们可以发现,明明进程A的UI线程正在等待ServiceB的返回结果,处于休眠的状态,竟然有空闲去响应进程B发起的ServiceA的Binder调用。
我们把ServiceeA的get msg时候堆栈打出来看看。
代码语言:javascript复制KobeWang: java.lang.Exception: KobeWang
//这个线程莫名的被Binder驱动唤醒去响应ServiceA的Binder请求
V KobeWang: at com.kobe.binderlock.MainActivity$ServiceA.sendMsg(MainActivity.java:21)
V KobeWang: at com.kobe.binderlock.IServiceA$Stub.onTransact(IServiceA.java:61)
V KobeWang: at android.os.Binder.execTransactInternal(Binder.java:1035)
V KobeWang: at android.os.Binder.execTransact(Binder.java:1008)
//这下面是serviceB.sendMsg(mServiceA, "hello ServiceB");调用之后,休眠在Binder驱动
V KobeWang: at android.os.BinderProxy.transactNative(Native Method)
V KobeWang: at android.os.BinderProxy.transact(BinderProxy.java:510)
V KobeWang: at com.kobe.binderlock.IServiceB$Stub$Proxy.sendMsg(IServiceB.java:96)
V KobeWang: at com.kobe.binderlock.MainActivity$1.onServiceConnected(MainActivity.java:38)
V KobeWang: at android.app.LoadedApk$ServiceDispatcher.doConnected(LoadedApk.java:1948)
V KobeWang: at android.app.LoadedApk$ServiceDispatcher$RunConnection.run(LoadedApk.java:1980)
V KobeWang: at android.os.Handler.handleCallback(Handler.java:883)
V KobeWang: at android.os.Handler.dispatchMessage(Handler.java:100)
V KobeWang: at android.os.Looper.loop(Looper.java:214)
V KobeWang: at android.app.ActivityThread.main(ActivityThread.java:7501)
V KobeWang: at java.lang.reflect.Method.invoke(Native Method)
V KobeWang: at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
V KobeWang: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:935)
看完堆栈就应该明白这一切都是Binder驱动搞的鬼,Binder驱动发现反正进程A的UI线程为了等ServiceB的结果休眠中,既然ServiceB又要请求进程A的ServiceA,与其采用进程A的Binder线程响应,还不如直接用进程A休眠的UI线程响应,这样子进程A的线程使用就从2个减少为1个
总结
这个机制除了这种两个进程互相Binder调用的情况,就算是3个进程,4个进程,5个进程,甚至n个进程产生嵌套的Binder调用,也一样可以发挥作用,发挥作用的规则如下图描述:
当进程D发起Binder调用到进程B的时候,进程D会向后遍历整个Binder调用关系。检查是否已经有进程B参与,如果已经进程B参与了,直接唤醒进程B中参与本次Binder嵌套调用中休眠的线程,响应进程D对进程B的Binder调用
彩蛋
其实一般来说对于普通工程师,了解清楚这个规则就够了,以后开发过程中注意一下这种Binder的嵌套调用即可。Binder驱动是如何实现线程栈复用?我清楚背后实现的原理,我还没有准备好如何通俗易懂地讲出来,需要提前准备的知识太多,有兴趣的朋友可以看《Android系统源代码情景分析》。