通过service分析安卓binder机制的进程间通信

安卓中进程间通信大家可能都非常了解,我们知道service可以使用aidl方法,远程调用服务实现两个不同app的通信。根据文档也能很快实现功能,但具体是怎么跨越两个进程进行通信的,现在跟着我的脚步分析分析吧。
首先提出产生困惑的几个问题:

  1. 哪些代码是在哪些进程或线程中运行的。
  2. 是如何传递数据的。
  3. 是如何传递方法。

代码角度分析

server端

首先service通过binder机制的通信是属于server-client模式的,server端为Service类,在Service中需要实现onBind方法,该方法返回实现了IBinder的实例,这里类实例也通常是在Service中实现的。

 @Override
    public IBinder onBind(Intent intent) {
        initSum = intent.getIntExtra("initSum", -1);
        Log.d("jw", "onBind: Thread:"+Thread.currentThread().getId());
        return remoteService;
    }

    IBinder remoteService = new IMyAidlInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
        
        }
        
        @Override
        public int add(int a, int b) throws RemoteException {
            Log.d("jw", "add thread: "+Thread.currentThread().getId());
            return a+b+initSum;
        }
   }

client端

在client端,示例代码如下:

ServiceConnection mAddServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            addService = IAddAidlInterface.Stub.asInterface(service);

            serviceConnected = true;
            tv.setText("bind success");

        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            serviceConnected =false;
        }
    };

    public void bindAddService(View view) {
        Intent intent = new Intent();
        intent.setClassName("com.yglx.learnservice", "com.yglx.learnservice.MyService");
        bindService(intent, mAddServiceConnection, BIND_AUTO_CREATE);
        Log.d("jw", "bindAddService thread: "+Thread.currentThread().getId());
    }

客户端在和服务器连接成功后,拿到了一个实现了某些接口(即客户端和服务器端端aidl生成的
接口)的实例,之后只需要用这个实例就可以访问相应的接口了。

交互

可以看出直接使用不是很难,咋一看就是这么一回事的:
1, 客户端bindservice。
2, 神秘的力量判断这个service是否有binder,如果没有,代码执行3。如果有binder直接运行4。
3, 服务端运行onbind返回一个binder。
4, 又一股神秘的力量,把binder拿过来,让代码运行5。
5, 客户端运行ServiceConnection中的回调函数,把binder给到客户端。
6, 客户端愉快的开始通过那个binder和服务端通信了。
7, 客户端运行binder的相关方法,实际应该是一个写入虚拟内存的过程。
8, 内核的神秘力量又弄出一个线程来,运行服务端端代码,即告诉服务端可以读虚拟内存的数
据了,然后返回到内核。
9, 内核的神秘力量又把给到的数据,让刚才客户端的线程继续运行下去。
10,客户端和服务器就圆满完成了一次跨进程通信了。
以上流程基本就说明了,在应用层进程间是如何通信的。

进程间都有哪些线程

先讲我们最熟悉的主线程。(这里为了让大家看清都涉及到的线程,所以假设服务端和客户端都在代码上都没有显示的开启线程)

  1. 当客户端调用binderSevice方法时,这个时候处于客户端主线程。
  2. 如果service没有启动,则在service的oncreate生命周期中为服务端主线程。
  3. 如果未有binder绑定过,则服务端回调生命周期方法onbind为服务端主线程。
  4. 客户端回调ServiceConnection的方法,这个也是客户端主线程。
  5. 客户端发起binder的相关方法,这个时候在客户端主线程。
  6. 这时服务端要开始运行协议中的指定的binder方法了,这里要分两种情况,一种是如果客户端和服务端在同一个进程,则此时运行的协议方法也是和客户端一样的线程;如果客户端和服务
    端不在同一个进程,则服务端将会有一个新的线程来执行协议的指定方法。
  7. 客户端得到binder相关方法端返回值,如果有返回值的话,这里是同步进行的,即客户端将阻塞直到服务端返回数据,这里还是客户端的主线程(所以客户端运行远程调用方法时,不应该在主线程中执行)。

到这里,第一个问题解决。

传递数据

说传递数据的时候,先简单说一下自动生成的aidl文件。
接口文件中有一个继承了binder的抽象类Stub,Stub里有一个实现了接口的Proxy代理类。这里我注意到一点,其实我们不管在客户端还是服务端都用的同一个aidl文件生成的,并且放在aidl文件的包必须一致才不会出错。在我的测试代码中,为了在接口文件中能获取一些日志信息,所以我直接把在服务端生成的接口文件拷贝到测试的应用工程中。在对日志分析时,注意到,这个文件其实是有两个功能的,一个是用于服务端的主要是抽象类Stub,另一个是在客户端应用中使用的Proxy代理类。所以在服务器端自己不会作为客户端的话,可以把Proxy类去除。如果客户端不会用作服务器的话,也完全可以把Stub去除,把Proxy类抽离出来就可以。
数据传输中,并不是所有数据都能传输的,除了基本类型外,则是需要实现了parcel的可序列化的类才能传递,和Intent传输的数据类似(Intent其实也是一种进程间通信数据传输的载体)。

  1. 客户端调用binder的协议方法时,运行Proxy类中的方法。
    比如demo中的add方法:
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(a);
_data.writeInt(b);
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
_reply.readException();

流程是把数据的写入和读出都是通过Parcel,这也就是类需要实现Parcel的原因。

  1. 客户端调用mRetome.transact之后,经过底层binder的神秘力量,将会调用服务端transact方法,即服务端的Stub里的onTransact方法:
case TRANSACTION_add: {
    data.enforceInterface(DESCRIPTOR);
    Log.d("jw", "aidl stub onTransact: Thread:"+Thread.currentThread().getId());
    int _arg0;
    _arg0 = data.readInt();
    int _arg1;
    _arg1 = data.readInt();
    int _result = this.add(_arg0, _arg1);
    reply.writeNoException();
    reply.writeInt(_result);
    return true;
}

可以看到是先从data中读取底层传输过来的数据,然后取出对应的数据,调用相应的服务端已经实现了的this.add方法,用reply把数据写回底层binder。
好了,数据传输流程就这样的。

传递方法

传递方法即传递回调方法(本质上传的是一个binder,如同binderService返回一个binder),>即有的时候需要服务端主动发消息,能让客户端收到。这个时候就可以通过客户端把回调接口注册到服务端中,服务端通过RemoteCallbackList把这些接口(binder)保存起来,当需要向客户端发送消息的时候即可遍历里面的接口把数据通知过去。在回调的时候其实就相当于客户端这个时候充当了服务端。
客户端把binder写入到data中

_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((messageReceiver != null)) ? (messageReceiver.asBinder()) : (null)));
mRemote.transact(Stub.TRANSACTION_registerReceiver, _data, _reply, 0);
_reply.readException();

在服务端注册时返回接口,如同binderService获取到Ibinder一样。

case TRANSACTION_registerReceiver: {
   data.enforceInterface(DESCRIPTOR);
   com.yglx.learnservice.MessageReceiver _arg0;
   _arg0 = com.yglx.learnservice.MessageReceiver.Stub.asInterface(data.readStrongBinder());
   this.registerReceiver(_arg0);
   reply.writeNoException();
   return true;
 }

总结

好了一开始提出的三个问题也都解决了。本文主要也是在应用层角度对android进程间通信通过service的一些记录,特别在说到binder底层机制上,只用了神秘力量来解释也是为了不偏离应用层这个高度。能够清楚应用层的这些情况,就应该知道在service时,需要注意一些不要在主线程操作,以及时刻注意处理并发问题。
最后附上分析时用的代码,读者可以通过拷贝build里aidl生成的接口文件,然后添加日志的方式,查看线程的情况,当然笔者代码也有部分日志输出代码,记得在选中log输出时对应用选择no-filters。

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

推荐阅读更多精彩内容