[031]Binder线程栈复用

2020-06-08 12:00:57 浏览数 (1)

前言

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系统源代码情景分析》。

0 人点赞