[031]Binder线程栈复用

前言

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

interface IServiceA {
    void sendMsg(String msg);
}

关键代码

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

interface  IServiceB {
    void sendMsg(IBinder binder, String msg);
}

关键代码

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运行在另外一个进程,否则就会触发另一个机制:"远程转本地"

<service android:name=".ServerB"
    android:exported="true"
    android:process=":server">
</service>

三、运行结果

//运行在进程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

看到结果,我们简化一下

//运行在进程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时候堆栈打出来看看。

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,036评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,046评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,411评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,622评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,661评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,521评论 1 304
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,288评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,200评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,644评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,837评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,953评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,673评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,281评论 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,889评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,011评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,119评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,901评论 2 355