IPC -通过AIDL看Binder的进程间通信过程

AIDL是 Android Interface definition language的缩写,即:Android接口定义语言。

进程隔离:不同进程间不可以相互访问内存空间,要想相互调用,必须要进行进程间通信。

本篇中不涉及Binder的底层原来,但是要理解一个知识点:客户端进程持有BinderProxy类的对象,通过Binder驱动,向对应的运行在服务端进程中的Binder对象发送消息(执行远程方法调用)。可以类比java的Socket编程中的,Socket 向SocketServer发送消息的过程,不过Binder不仅仅是发送报文消息那么简单,他对远程方法调用实现了封装。

注:这不是一篇介绍如何使用AIDL的文章(如果想学习aidl如何使用移步官网案例任玉刚博客),这篇文章主要解读编译系统根据aidl文件生成的java代码,目的是为了将来读懂ActivityManagerService的源码。

AIDL生成代码解析

为了便于阅读生成的代码,我们来写一个最最简单的AIDL,让服务端为我们实现两个数相加的功能。客户端界面如下:

AIDL客户端界面 2017-09-12 18.01.31.png

首先定义AIDL接口:

// ICalculate.aidl
package me.febsky.aidl;

interface ICalculate {
    int add(int a, int b);
}

然后在AndroidStudio中运行Build-->Rebuild Project或者点击Gradle同步按钮,这时候会在app-->build-->generated-->source-->aidl下面生成ICalculate.java这个类。这些代码是自动生成的,不可修改,应该说改了也不起作用,下次编译还会被覆盖。

AIDL代码生成位置 2017-09-12 18.11.05.png

打开这个类文件,来看下生成的源码(为了便于阅读,在Mac上可以按command+alt + L来格式化代码),现在摘录代码如下:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /Users/liuqiang/Desktop/AIDL/app/src/main/aidl/me/febsky/aidl/ICalculate.aidl
 */
package me.febsky.aidl;

public interface ICalculate extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements me.febsky.aidl.ICalculate {
        private static final java.lang.String DESCRIPTOR = "me.febsky.aidl.ICalculate";

        /**
         * Construct the stub at attach it to the interface.
         */
        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }

        /**
         * Cast an IBinder object into an me.febsky.aidl.ICalculate interface,
         * generating a proxy if needed.
         */
        public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
                return ((me.febsky.aidl.ICalculate) iin);
            }
            return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
        }

        @Override
        public android.os.IBinder asBinder() {
            return this;
        }

        @Override
        public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(DESCRIPTOR);
                    return true;
                }
                case TRANSACTION_add: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    int _arg1;
                    _arg1 = data.readInt();
                    int _result = this.add(_arg0, _arg1);
                    reply.writeNoException();
                    reply.writeInt(_result);
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements me.febsky.aidl.ICalculate {
            private android.os.IBinder mRemote;

            Proxy(android.os.IBinder remote) {
                mRemote = remote;
            }

            @Override
            public android.os.IBinder asBinder() {
                return mRemote;
            }

            public java.lang.String getInterfaceDescriptor() {
                return DESCRIPTOR;
            }

            @Override
            public int add(int a, int b) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                int _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(a);
                    _data.writeInt(b);
                    mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
                    _reply.readException();
                    _result = _reply.readInt();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_add = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }

    public int add(int a, int b) throws android.os.RemoteException;
}

好吧,怪不得Android要搞出AIDL这么个东西,我在aidl中就写了一行代码,系统生成了这么多,要不是系统自动生成,每次用到Binder和AIDL进行进程间通信的时候,都要手撸这么写重复代码。

这些代码从何看起呢,为了便于从宏观上观察生成的代码,我们折叠一下暂时不关心的代码:

代码总览1 2017-09-12 18.17.35.png

然后是这样:

代码总览2 2017-09-12 18.19.25.png

最后是这么个样子:

代码总3 2017-09-12 18.23.30.png

从以上代码来看,在生成的java文件中主要有三个类:ICalculateICalculate.StubICalculate.Proxy。其中Stub是接口ICalculate的静态内部类,Proxy是Stub的私有静态内部类(个人认为其实StubProxy没有必要一定要做为ICalculate的静态内部类,这样放置只是为了便于管理和查看他们之间的关联关系)

ICalculate

这个接口其实很简单继承与IInterface,先不用管这个IInterface的作用,只看ICalculate的话就是个普通的接口,这里面有我们定义的add方法,就是定了了我们要在AIDL中实现的业务逻辑。这个接口其实为了进程间通信,所有定义的是客户端需要服务端提供的功能。

ICalculate.Stub

这个类很重要,它继承了Binder类,实现了ICalculate接口。从继承关系来看他是一个具有ICalculate功能的Binder。好,既然是一个Binder就具有了进程间通信的功能。注意这个类是个抽象类,它只是定义了Binder的业务层通信功能,但是具体的通信内容(也就是我们的业务方法add方法)并没有具体实现,需要子类来实现。一般Stub的子类在服务端实现。

说到这里必须说下Binder,Binder是Android上比较复杂的一个东西了。但这里我们不分析Binder的通信原理。只需要知道,Binder和BinderProxy是成对出现的,客户端进程持有BinderProxy对象,然后BinderProxy可以和binder驱动交互,binder驱动再去发消息给Binder对象从而实现IPC。个人认为为了便于理解,完全可以把BinderProxy和Binder类比成javaTCP 中的Socket和SocketServer

看下Binder的源码结构:

Binder 和BinderProxy 源码2017-09-13 14.30.06.png

Binder和BinderProxy只是实现了进程间通信功能,具体通信内容是啥他不关心。通信内容交给ICalculate.Stub.Proxy 和 ICalculate.Stub的子类来实现。

在ICalculate.Stub中有几个很重要的方法:

  • asInterface
  • onTransact

首先看看asInterface方法,这个方法是一个静态方法,我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过这个方法拿到一个远程的service(这个Service不是Android的四大组件的那个Service)的代理(客户端和服务端不在同一个进程中的情况下),binderService时候的代码如下:

    ICalculate calculate;

    ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            calculate = ICalculate.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

        }
    };
//一般情况下,如果是跨进程的穿件来的参数都是BinderProxy类型的
public static me.febsky.aidl.ICalculate asInterface(android.os.IBinder obj) {
    //这种情况基本不存在,可以忽略,你传了个null进来大家还玩啥?
    if ((obj == null)) {    
        return null;
    }
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    //这个if来判断,客户端和服务端是不是在一个进程中
    //也就是来判断,传进来的参数obj是Binder对象还是BinderProxy对象
    //如果在同一个进程中传入的是Binder对象,也即是Stub子类的对象,以下if语句成立
    if (((iin != null) && (iin instanceof me.febsky.aidl.ICalculate))) {
          return ((me.febsky.aidl.ICalculate) iin);
    }
    return new me.febsky.aidl.ICalculate.Stub.Proxy(obj);
}

onTransact后面我们和Proxy 中的transact方法一块分析。接下来先看Stub.Proxy这个类。

ICalculate.Stub.Proxy

Stub.Proxy 2017-09-13 15.35.57.png

从代码中可以看出,这个类的构造方法接收一个IBinder的实现类,其实这里主要是BinderProxy的对象。然后忽略其他,直接看我们的add方法。前面也提到了,客户端通过Stub.asInterface 静态方法,持有Stub.Proxy 类的对象,然后和存在于服务端进程中Stub子类的对象进行通信。其实归根到底是客户端BinderProxy和服务端Binder的通信。
这个add方法可以解读为,Stub.Proxy 类的对象,持有BinderProxy的对象,通过BinderProxy对象,像远程的Binder对象发送消息。看下发送消息的主要代码。可以先不用去管Parcel对象,可以把它看做一个可以序列化的对象,或者向远程发送数据的载体。把要传递给远程对象的参数放到Parcel中,然后调用BinderProxy的transact 方法,发送消息到Stub子类对象中。
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
这个方法由三个参数

  • Stub.TRANSACTION_add 方法名的唯一标示,告诉远程对象,我要执行你的哪个方法,目前我们的接口中只定义了一个add方法,
  • _data封装了要调用的远程方法的所有的需要的参数
  • _reply 远程方法返回值的载体
  • flag 最后一个参数是个flag,默认0就可以了,好像是用来指定是不是单向的IPC的

通过以上过程,这样一个远程方法调用,就会通过Binder机制,把消息(调用某个方法)发送到服务端进程中的相应对象中。我们在此依然忽略BinderProxy和Binder之间跨进程通信的底层原理,只要知道,BinderProxy通过调用transact 方法,能通过Binder驱动,发消息到Binder进程就可以了。继续分析当BinderProxy通过transact 方法发送消息到服务端Binder子类对应的进程的时候,Stub的子类是如何接收处理这个消息的,看Stub类的onTransact方法:

Stub中的onTransact 方法 2017-09-13 16.08.30.png

可以看到在这个方法中有个switch语句,这个code就是刚刚在transact中的第一参数,用了标志该调用哪个方法。其余方法也和transact 中的一一对应,不再解释。其实上面的代码也很好理解,主要看第二个case里面语句吗,先把方法需要参数从data这个载体中读出来,对应 BinderProxy transact方法的写入操作,然后调用真正的业务方法addint _result = this.add(_arg0, _arg1);并把返回值写入到reply 这个返回值载体中从而能把方法返回值传递个客户端。从上面可以看到Stub是个抽象类,并没有实现业务方法add,这个要在他的子类中实现,具体代码如下:

//注意这个Service要放到单独的进程中运行
public class CalculateService extends Service {

    private ICalculate.Stub calculate = new ICalculate.Stub() {
        @Override
        public int add(int a, int b) throws RemoteException {
            return a + b;
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return calculate;
    }
}

【[测试代码下载]
(http://download.csdn.net/download/niyingxunzong/9977048)】

测试效果图:

测试效果图 2017-09-13 16.57.14.png

重要知识点

在使用AIDL的时候,编译工具会给我们生成一个Stub的静态内部类;这个类继承了Binder, 说明它是一个Binder本地对象,它实现了IInterface接口,表明它具有远程Server承诺给Client的能力;Stub是一个抽象类,具体的IInterface的相关实现需要我们手动完成,这里使用了策略模式。

其中基本的UML类图如下,类图中并没有标注出所有的方法,只是标注了我们关心的几个:

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

推荐阅读更多精彩内容