前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >[031]Binder线程栈复用

[031]Binder线程栈复用

作者头像
王小二
发布2020-06-08 12:00:57
6330
发布2020-06-08 12:00:57
举报

前言

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

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 一、假设一个场景
  • 二、写个Demo
    • 2.1 进程A
      • 2.2 进程B
      • 三、运行结果
      • 总结
      • 彩蛋
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档