Framework 源码解析知识梳理(3) - 应用进程之间的通信实现

一、前言

Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现Framework 源码解析知识梳理(2) - 应用进程与 WMS 的通信实现 这两篇文章中,我们介绍了应用进程与AMS以及WMS之间的通信实现,但是逻辑还是比较绕的,为了方便大家更好地理解,我们介绍一下大家见得比较多的应用进程间通信的实现。

二、例子

说起应用进程之间的通信,相信大家都不陌生,应用进程之间通信最常用的方式就是AIDL,下面,我们先演示一个AIDL的简单例子,接下来,我们再分析它的内部实现。

2.1 服务端

2.1.1 编写 AIDL 文件

第一件事,就是服务端需要声明自己可以为客户端提供什么功能,而这一声明则需要通过一个.aidl文件来实现,我们先简要介绍介绍一下AIDL文件:

  • AIDL文件的后缀为*.aidl
  • 对于AIDL默认支持的数据类型,是不需要导入包的,这些默认的数据类型包括:
  • 基本数据类型:byte/short/int/long/float/double/boolean/char
  • String/CharSequence
  • List<T>:其中T必须是AIDL支持的类型,或者是其它AIDL生成的接口,或者是实现了Parcelable接口的对象,List支持泛型
  • Map:它的要求和List类似,但是不支持泛型
  • Tag标签:对于接口方法中的形参,我们需要用in/out/inout三种关键词去修饰:
  • in:表示服务端会收到客户端的完整对象,但是在服务端对这个对象的修改不会同步给客户端
  • out:表示服务端会收到客户端的空对象,它对这个对象的修改会同步到客户端
  • inout:表示服务端会收到客户端的完整对象,它对这个对象的修改会同步到客户端

说了这么多,我们最常见的需求无非就是两个:让复杂对象实现Parcelable接口实现传输以及定义接口方法。

(1) Parcelable 的实现

我们可以很方便地通过AS插件来让某个对象实现Parcelable接口,并自动补全要实现的方法:


安装完之后重启,我们原本的对象为:

public class InObject {

    private int inData;

    public int getInData() {
        return inData;
    }

    public void setInData(int inData) {
        this.inData = inData;
    }
    
}

在文件的空白处,点击右键Generate -> Parcelable


它就会帮我们实现Parcelable中的接口:

public class InObject implements Parcelable {

    private int inData;

    public int getInData() {
        return inData;
    }

    public void setInData(int inData) {
        this.inData = inData;
    }

    public InObject() {}

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(this.inData);
    }

    protected InObject(Parcel in) {
        this.inData = in.readInt();
    }

    public static final Parcelable.Creator<InObject> CREATOR = new Parcelable.Creator<InObject>() {
        @Override
        public InObject createFromParcel(Parcel source) {
            return new InObject(source);
        }

        @Override
        public InObject[] newArray(int size) {
            return new InObject[size];
        }
    };
}

(2) 编写 AIDL 文件

点击File -> New -> AIDL -> AIDL File之后,会多出一个名叫aidl的文件夹,之后我们所有需要用到的aidl文件都被存放在这里:


初始时候,AS为我们生成的AIDL文件为:

// AIDLInterface.aidl
package com.demo.lizejun.binderdemoclient;

// Declare any non-default types here with import statements

interface AIDLInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

当我们编译之后,就会在下面这个路径中生成一个Java文件:

/*
 * This file is auto-generated.  DO NOT MODIFY.
 * Original file: /home/lizejun/Repository/RepoGithub/BinderDemo/app/src/main/aidl/com/demo/lizejun/binderdemoclient/AIDLInterface.aidl
 */
package com.demo.lizejun.binderdemoclient;
// Declare any non-default types here with import statements

public interface AIDLInterface extends android.os.IInterface {
    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements com.demo.lizejun.binderdemoclient.AIDLInterface {
        private static final java.lang.String DESCRIPTOR = "com.demo.lizejun.binderdemoclient.AIDLInterface";

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

        /**
         * Cast an IBinder object into an com.demo.lizejun.binderdemoclient.AIDLInterface interface,
         * generating a proxy if needed.
         */
        public static com.demo.lizejun.binderdemoclient.AIDLInterface asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.demo.lizejun.binderdemoclient.AIDLInterface))) {
                return ((com.demo.lizejun.binderdemoclient.AIDLInterface) iin);
            }
            return new com.demo.lizejun.binderdemoclient.AIDLInterface.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_basicTypes: {
                    data.enforceInterface(DESCRIPTOR);
                    int _arg0;
                    _arg0 = data.readInt();
                    long _arg1;
                    _arg1 = data.readLong();
                    boolean _arg2;
                    _arg2 = (0 != data.readInt());
                    float _arg3;
                    _arg3 = data.readFloat();
                    double _arg4;
                    _arg4 = data.readDouble();
                    java.lang.String _arg5;
                    _arg5 = data.readString();
                    this.basicTypes(_arg0, _arg1, _arg2, _arg3, _arg4, _arg5);
                    reply.writeNoException();
                    return true;
                }
            }
            return super.onTransact(code, data, reply, flags);
        }

        private static class Proxy implements com.demo.lizejun.binderdemoclient.AIDLInterface {
            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;
            }

            /**
             * Demonstrates some basic types that you can use as parameters
             * and return values in AIDL.
             */
            @Override
            public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeInt(anInt);
                    _data.writeLong(aLong);
                    _data.writeInt(((aBoolean) ? (1) : (0)));
                    _data.writeFloat(aFloat);
                    _data.writeDouble(aDouble);
                    _data.writeString(aString);
                    mRemote.transact(Stub.TRANSACTION_basicTypes, _data, _reply, 0);
                    _reply.readException();
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
            }
        }

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

    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}

整个结构图为:


有没有感觉在 Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现Framework 源码解析知识梳理(2) - 应用进程与 WMS 的通信实现 中也见到过类似的东西 asInterface/asBinder/transact/onTransact/Stub/Proxy...,这个我们之后再来解释,下面我们介绍服务端的第二步操作。

2.1.2 编写 Service

既然服务端已经定义好了接口,那么接下来就服务端就需要实现这些接口:

public class AIDLService extends Service {

    private final AIDLInterface.Stub mBinder = new AIDLInterface.Stub() {
        @Override
        public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
            Log.d("basicTypes", "basicTypes");
        }
    };

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

在这个Service中,我们只需要实现AIDLInterface.Stub接口中的basicTypes接口就可以了,它就是我们在AIDL文件中定义的接口,再把这个对象通过onBinde方法返回。

2.1.3 声明 Service

最后一步,在AndroidManifest.xml文件中声明这个Service

        <service
            android:name=".server.AIDLService"
            android:enabled="true"
            android:exported="true">
        </service>

2.2 客户端

2.2.1 编写 AIDL 文件

和服务端类似,客户端也需要和服务端一样,生成一个相同的AIDL文件,这里就不多说了:

// AIDLInterface.aidl
package com.demo.lizejun.binderdemoclient;

// Declare any non-default types here with import statements

interface AIDLInterface {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
    void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,
            double aDouble, String aString);
}

和服务端类似,我们也会得到一个由aidl生成的Java接口文件。

2.2.2 绑定服务,并调用

(1) 绑定服务

    private void bind() {
        Intent intent = new Intent();
        intent.setComponent(new ComponentName("com.demo.lizejun.binderdemo", "com.demo.lizejun.binderdemo.server.AIDLService"));
        boolean result = bindService(intent, mServiceConnection, BIND_AUTO_CREATE);
        Log.d("bind", "result=" + result);
    }

    private ServiceConnection mServiceConnection = new ServiceConnection() {

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinder = AIDLInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mBinder = null;
        }
    };

(2) 调用接口

    public void sayHello(View view) {
        try {
            if (mBinder != null) {
                mBinder.basicTypes(0, 0, false, 0, 0, "aaa");
            }
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

之后我们打印出响应的log

三、实现原理

下面,我们就一起来分析一下通过AIDL实现的进程间通信的原理。

**(1) 客户端获得服务端的远程代理对象 IBinder **

我们从客户端说起,当我们在客户端调用了bindService方法之后,就会启动服务端实现的AIDLService,而在该AIDLServiceonBind()方法中返回了AIDLInterface.Stub的实现类,绑定成功之后,客户端就通过onServiceConnected的回调,得到了它在客户端进程的远程代理对象IBinder

(2) AIDLInterface.Stub.asInterface(IBinder)

在拿到这个IBinder对象之后,我们通过AIDLInterface.Stub.asInterface(IBinder)方法,对这个IBinder进行了一层包装,转换成为AIDLInterface接口类,那么这个AIDLInterface是怎么来的呢,它就是通过我们在客户端定义的aidl文件在编译时生成的,可以看到,最终asInterface方法会返回给我们一个AIDLInterface的实现类ProxyIBinder则被保存为它内部的一个成员变量mRemote


也就是说,我们在客户端中保存的mBinder实际上是一个由AIDL文件所生成的Proxy对象。

(3) 调用 AIDLInterface 的接口方法

Proxy实现了AIDLInterface中定义的接口方法,当我们调用它的接口方法时:


实际上是通过它内部的mRemote对象的transact方法发送消息:

(4) 服务端接收消息

那么客户端发送的这一消息去哪里了呢,回想一下在服务端中通过onBind()放回的IBinder,它其实是由AIDL文件生成的AIDLInterface.java中的内部类Stub的实现,而在Stub中有一个回调函数onTransact,当我们通过客户端发送消息之后,那么服务端的Stub对象就会通过onTransact收到这一发送的消息:

(5) 调用子类的处理逻辑

onTransact方法中,又会去调用AIDLInterface定义的接口:

而我们在服务端AIDLService中实现了该接口,因此,最终就打印出了我们在上面所看到的文字:

四、进一步讨论

现在,我们通过这一进程间的通信过程来复习一下前面两篇文章中讨论的应用进程与AMSWMS之间的通信实现。

Framework 源码解析知识梳理(1) - 应用进程与 AMS 的通信实现 中,AMS所在进程在收到消息之后,进行了下面的处理逻辑:


这里面的处理逻辑就相当于我们在客户端中onServiceConnected中做的工作一样,IApplicationThread实际上是一个ApplicaionThreadProxy对象,和我们上面AIDLInterface实际上是一个AIDLInterface.Stub.Proxy的原理是一样的。当我们调用了它的接口方法时,就是通过内部的mRemote发送了消息。

AMS通信中,接收消息的是应用进程中的ApplicationThread,而我们上面例子中接收消息的是服务端进程的AIDLInterface.Stub的实现类mBinder,同样是在onTransact()方法中接收消息,再由子类去实现处理的逻辑。

Framework 源码解析知识梳理(2) - 应用进程与 WMS 的通信实现 中的原理就更好解释了,因为它就是通过AIDL来实现的,我们从客户端向WMS所在进程建立会话时,是通过IWindowSession来实现的,会话过程中IWindowSession就对应于AIDLInterface,而WMS中的IWindowSession.Stub的实现类Session,就对应于上面我们在AIDLService中定义的AIDLInterface.Stub的实现类mBinder

五、小结

其实AIDL并没有什么神秘的东西,它的本质就是Binder通信,我们定义aidl文件的目的,主要有两个:

  • 为了让应用进程间通信的实现者不用再去编写transact/onTransact里面的代码,因为这些东西和业务逻辑是无关的,只不过是简单的发送消息、接收消息。
  • 让服务端、客户端只关心接口的定义。

如果我们明白了AIDL的原理,那么我们完全可以不用定义AIDL文件,自己去参考由AIDL文件所生成的Java文件的逻辑,进行消息的发送和处理。


更多文章,欢迎访问我的 Android 知识梳理系列:

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

推荐阅读更多精彩内容

  • Jianwei's blog 首页 分类 关于 归档 标签 巧用Android多进程,微信,微博等主流App都在用...
    justCode_阅读 5,896评论 1 23
  • Android跨进程通信IPC整体内容如下 1、Android跨进程通信IPC之1——Linux基础2、Andro...
    隔壁老李头阅读 10,720评论 13 43
  • 序:很多都是自己的个人理解,不一定非常准确,供大家参考学习 大家应该都用过进程间的通讯,那有没有想过一个问题,进程...
    _水蓝阅读 936评论 0 3
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,598评论 18 139
  • 前几天有小伙伴在群里一直咨询,为什么同样是追踪同一个指数的基金,收益有差别?为什么选择这个指数基金,而不是那个指数...
    smallfen阅读 888评论 1 9