跨进程通讯Binder的onTransact方法一定在binder线程池的binder线程中执行吗?

一.引言

在大多数人的印象中,在Android中用Binder机制进行跨进程通讯,Binder会在onTransact方法中处理Binder驱动发送过来的消息,这个方法会运行在Binder驱动所管理的Binder线程池中(Binder线程的创建和销毁是在用户空间,但是管理是由Binder驱动代为管理的)。
其实这么说是不对的,片面的,在大多数情况下,onTransact()方法的确是在Binder线程中处理Binder驱动发送过来的消息。除了一种情况,在两个进程中都存在Binder实体对象,相互发送远程请求的时候。
对Android的Binder机制有一定了解的朋友一定都知道,在一次跨进程通讯过程中主要涉及到四种类型的Binder。Server端持有Binder的本地对象,在本地的Binder创建的时候,在Binder驱动中会生成一个Binder实体对象,对应Server端的本地对象。在Client端向Server端发送请求的时候,我们会创建一个Binder代理对象,同样的Binder驱动会为我们创建一个Binder引用对象。


下面就用实际的例子为大家演示上面所说的特殊情况。实例通过AIDL来实现跨进程通讯(AIDL基于Binder,简化了Binder跨进程r通讯的实现。)

二.创建两个AIDL接口文件

首先我们创建两个AIDL文件。如下图所示:
在这里插入图片描述
在这里插入图片描述
IBinder类型的对象是可以用于跨进程通讯传输的(在Android中应用程序所在的进程,和AMS进行通讯,就是传递了一个本地的Binder对象(即ApplicationThread)过去方便AMS与应用程序进行通讯)。下面编译我们的应用程序。在这里插入图片描述
系统为我们生成了两个java文件,把这两个文件copy到我们的包下,方便对它们进行修改。然后删除我们的AIDL文件,重新编译项目。

三:创建一个Service组件

现在,我们再创建一个Service组件MyService,注意在注册MyService组件时,要设置它的
android:process=".remote"属性值,指明它要运行在remote进程中,这样当Activity绑定它的时候,AMS发现MyService将要运行的进程不存在,便会向Zygote进程请求它创建一个新的进程以便我们的MyService运行。我们在MyService中创建一个本地的Binder,然后在onBind方法中返回它。
MyService主要代码如下:

    private IMyServerAidlInterface.Stub serverBinder = new IMyServerAidlInterface.Stub() {
        @Override
        public String getServerStr(IBinder binder) throws RemoteException {
            Log.v("MyServiceServer",Thread.currentThread().getName());
            IClientAidlInterface clientBinder = IClientAidlInterface.Stub.asInterface(binder);
            clientBinder.doToastString("skt");
            return "你好";
        }
    };
    
    @Override
    public IBinder onBind(Intent intent) {
        return serverBinder;
    }

四.在Activity中创建本地Binder

在Activity启动的时候绑定我们的MyService,同时在onServiceConnected回调中传入我们的本地Binder到服务端。主要代码如下。

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Intent intent  = new Intent(this, MyService.class);
        bindService(intent,serviceConnection,BIND_AUTO_CREATE);
    }

   private ServiceConnection serviceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
            IMyServerAidlInterface serverBinder = IMyServerAidlInterface.Stub.asInterface(iBinder);
            try {
                String serverStr = serverBinder.getServerStr(clientBinder);
                Log.v("MyServiceClient", Thread.currentThread().getName());
                Log.v("MyServiceClient",serverStr);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName componentName) {

        }
    }

    private IClientAidlInterface.Stub clientBinder = new IClientAidlInterface.Stub() {
        @Override
        public void doToastString(String str) throws RemoteException {
            Log.v("MyServiceClient", Thread.currentThread().getName());
            Log.v("MyServiceClient", str);
        }
    };

同样在上面复制的两个java文件中添加一些log打印。
在这里插入图片描述在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
下面执行我们的应用程序,log打印如下:
在这里插入图片描述
我们可以清楚的看到两次跨进程通讯请求都得到了正确的结果,但是在Activity中的Binder的onTransact方法和我们的实现的接口方法,确确实实的运行在了主线程,而不是我们的Binder线程。服务端的Binder回调是在我们的Binder线程中的。
那为什么会导致这样的情况呢?

其实这是Binder为我们做的小优化。大家想,我们在onServiceConnected的方法中,通过serverBinder向服务端请求通信(AIDL的实现默认同步,transact方法的flag参数默认为0。),因为我们的onServiceConnected是运行在主线程的。所以主线程暂时被挂起,等待远程通信的响应。
服务端收到Client端的请求后,执行onTransact方法然后调用getServerStr()方法,这两个方法是运行在服务端Binder线程中的,没毛病。然后我们在getServerStr()中通过我们传过来的Binder代理,向客户端发起一个远程通信,按理说当客户端收到这个请求后,会在客户端的Binder线程中处理。其实不然,我们要知道现在客户端的主线程是被挂起的,它没事干,闲人一个,在等待我们服务端的返回,我们在处理客户端的远程请求时,还要向客户端发起远程请求,所以默认的,服务端的Binder线程同样会被阻塞,等待客户端的结果返回,客户端也是得不到回复的,在这样的情况下,客户端的主线程会被用来处理这次远程通信的请求(即向服务端发起远程通信请求的所在线程)。如果我们在客户端的子线程,向服务端发送远程通信请求,那么客户端的子线程会被拿来处理服务端向客户端发起的远程请求,因为你没事干啊,闲人一个。

修改Activity中的代码:
在这里插入图片描述
在执行我们的应用程序。log打印如下:
在这里插入图片描述
这回客户端的Binder的方法回调执行在了子线程,即我们启动远程服务所在的线程。

五.思考

那如果我们的主线程或者说我们请求远程服务的请求不给阻塞会出现什么的结果呢?
那客户端的Binder回调当然执行在客户端的Binder线程咯,下面我们来验证一下。
修改copy的IMyServerAidlInterface文件,找到它的transact方法,修改最后一个参数值为FLAG_ONEWAY。

 mRemote.transact(Stub.TRANSACTION_getServerStr, _data, _reply, FLAG_ONEWAY);

表示不需要关心服务端的回复。这样我们请求远程服务的线程就不会被阻塞。我们在运行我们的程序。打印如下:
在这里插入图片描述
可以看到我们客户端的Binder回调在客户端的Binder线程中。
同理,只要我们如果在Server端的Binder回调,开启一个异步线程,或者切换到主线中去向客户端发起一个远程请求,或者把它请求客户端的transact方法的flag参数改成 FLAG_ONEWAY,那样我们客户端的Binder回调肯定是我们客户端的Binder线程池中的。大家可以去验证一下。


转载标明原文地址哦:https://blog.csdn.net/braintt/article/details/88046162
参考文章:https://blog.csdn.net/universus/article/details/6211589
参看书籍:《Android内核剖析》《Android系统情景源码分析》

  • 4
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
是的,Binder进程间通信(IPC)机制可以实现进程调用原进程方法。 在Binder IPC,存在客户端和服务端两个角色。客户端通过Binder代理(Proxy)向服务端发送请求,而服务端通过Binder存根(Stub)接收请求并执行相应的方法。 当客户端向服务端发送请求时,可以通过Binder IPC机制实现进程调用原进程方法。具体步骤如下: 1. 客户端调用:客户端通过Binder代理(Proxy)调用服务端的方法,并传递相应的参数。 2. 数据传输:客户端将方法调用请求和参数打包成一个Parcel对象,并通过Binder IPC机制将该Parcel对象发送给服务端。 3. 服务端接收:服务端在接收到客户端的请求后,通过Binder存根(Stub)解析接收到的Parcel对象,并提取出方法调用请求和参数。 4. 方法执行:服务端根据解析得到的方法调用请求和参数,执行相应的方法。 5. 返回结果:在方法执行完毕后,服务端将返回的结果打包成一个Parcel对象,并通过Binder IPC机制将该Parcel对象发送回客户端。 6. 客户端接收:客户端在接收到服务端返回的结果后,通过Binder代理(Proxy)解析接收到的Parcel对象,提取出返回结果。 需要注意的是,进程调用原进程方法需要在服务端实现相应的方法,并且确保方法的调用参数和返回值能够正确地在进程间进行序列化和反序列化。此外,由于进程调用涉及到进程间的通信,因此需要注意线程安全和数据一致性的问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值