使用AIDL来进行进程间通讯

前言

AIDL它是Android众多进程间通讯方式中的一种,底层是Binder机制的实现,因此想要读懂AIDL自动生成的代码中各个类的做用,就必须对Binder有必定的了解,其实不止AIDL,Android进程间通讯的大多数方式的底层都是Binder机制,本文主要介绍AIDL的使用,因此不会介绍Binder机制的原理,关于Binder机制的原理,我推荐下面的一篇文章,零基础也能看懂:html

写给 Android 应用工程师的 Binder 原理剖析java

看完上面的文章你也就能对Binder原理的实现有大概的了解,对于咱们Android应用开发也就足够了,若是你还不知足,想从底层和源码了解Binder机制,你能够阅读下面的两个连接:android

Android Bander设计与实现git

Binder系列—开篇github

上面两个系列就是从设计和源码的角度去解读Binder,有点深刻。好了,对Binder有一个大致上的认识后,接下来咱们就要经过AIDL的使用来完成Android进程间通讯的实践。数组

Android进程间通讯的方式

在介绍AIDL以前,咱们先来看一下Android中除了AIDL还有哪些进程间通讯的方式:网络

一、Bundle架构

Bundle实现了Parcelable,因此在Android中咱们能够经过Intent在不一样进程间传递Bundle数据。并发

可是在Intent 传输数据的过程当中,用到了 Binder,Intent中的数据,即Bundle数据,会做为 Parcel 存储在Binder 的事务缓冲区(Binder transaction buffer)中的对象进行传输,而这个 Binder 事务缓冲区具备一个有限的固定大小,约为1MB,并且这个1Mb并非当前进程所独享,而是全部进程共享的,因此因为1Mb的限制,Bundle不能存放大量的数据,否则会报TransactionTooLargeException,而且Bundle中存放的数据也要求可以被序列化,因此Bundle只适用于数据量小和简单的进程间通讯。app

二、文件共享

两个进程间经过读写同一个文件来交换数据。

可是因为Android容许并发的对同一个文件读写,因此若是两个进程同时的对这个文件写,就会出现问题,因此这适用于对数据同步要求不高的进程间通讯。

三、ContentProvider

ContentProvider是Android中专门用于不一样应用之间,即不一样进程之间进行数据共享的方式,它的底层实现是Binder。

ContentProvider是四大组件之一,它的使用比较简单,咱们只须要继承自ContenProvider,并重写它的六个方法:onCreate、query、updata、insert、delete和getType,其中onCreate用于初始化,getType用于返回一个Uri请求表明的MIME类型,剩下的都是CRUD操做,除了onCreate方法运行在主线程,其余方法都运行在Binder线程池,而后在另一个进程中注册这个ContentProvider,在本进程的Activity中经过getContentResolver()得到ContentResolver后,再经过ContentResolver来完成对这个ContentProvider的CRUD操做。

四、套接字(Socket)

一种传统的Linux IPC方式,除了用于不一样进程之间还能够用于不一样机器之间(经过网络传输)的通讯,可是它的传输效率也是很是的低。

五、Messenger

经过Messenger能够在不一样进程之间传递Message对象,Message中能够放入咱们须要传递的数据,它的底层实现是AIDL。

可是Messenger在服务端的Handler是以串行的方式处理来自客户端的Message,因此若是有大量的并发请求,Messenger就效率低下,因此Messenger适用于数据并发量低的进程间通讯。

六、AIDL

也就是本文的主角,它的底层实现是Binder。

能够看到Android中进程间通讯的方式中,除了文件共享和Socket,底层都是须要经过Binder来传输数据,可见Binder对Android的进程间通讯来讲是多么的重要。

进程间通讯的准备

一、 序列化

Android中要在进程间传输的数据都要是可序列化的,序列化就是把对象转换成二进制(字节流),从而能够存储到存储设备或者经过网络进行传输,有序列化,就有反序列,反序列化就是把二进制(字节流)转化为对象,其中8种基本数据类型默承认以在进程间传输,没有序列化的概念,序列化的做用对象是对象。在Android中,对象经过实现Serializable或Parcelable接口来实现序列化,下面简单介绍一下它们之间使用和区别:

1.一、Serializable

Serializable是java中提供的一个序列化接口,它是一个空接口,对象实现这个Serializable接口,就标记这个对象是可序列化的,而后咱们经过ObjectOutputStream的writeObject方法就能完成对象的序列化,以下:

User user = new User();
ObjectOutputStream os = new  ObjectOutputStream(new FileOutputStream("D://test.txt"));
os.writeObject(user);
os.close();
复制代码

同理,经过ObjectOutputStream的readObject方法就能完成对象的反序列化,以下:

ObjectOutputStream in = new  ObjectInputStream(new FileInputStream("D://test.txt"));
User user = in.readObject();
in.close();
复制代码

若是你在序列化这个对象以后有可能会改变这个对象的类结构,例如为类添加新的字段,这时在序列化这个对象以前你就要为这个对象的类加上一个serialVersionUID参数,以下:

public class User{
    //serialVersionUID取什么值不要紧,只要保持不变就行
    private static final long serialVersionUID = 1L;
}
复制代码

若是你确保这个对象的类结构在序列化以后是一直不变的,那么这个serialVersionUID能够忽略,为何会这样呢?这是由于在序列化的时候,系统会把当前类的serialVersionUID写入到序列化的文件中,当反序列化时系统就会去检测文件中的serialVersionUID是否和当前类的serialVersionUID相同,若是相同就说明序列化的类和当前类的版本是相同的,这时候系统能够正确的反序列化,若是不一致,就说明序列化的类和当前类相比发生了某些变化,例如当前类添加了新的字段,这时就会反序列化失败,抛出异常。因此若是你手动指定了serialVersionUID的值,并一直保持不变,就算你改变了类的结构(不要改变类名和变量类型),序列化和反序列化时二者的serialVersionUID都是相同的,能够正常的进行反序列化,相反,若是你没有指定serialVersionUID的值,系统会根据类的结构自动生成它的hash值做为serialVersionUID的值,这时你改变了类的结构,在进行序列化和反序列化时二者生成的serialVersionUID不相同,这时反序列化就会失败,因此当你没有指定serialVersionUID的值,你要确保这个对象的类结构在序列化以后是一直不变。固然大多数状况下咱们仍是会指定serialVersionUID的值,以防咱们不当心修改类的结构致使没法恢复对象。

这里只是列举了用Serializable实现对象序列化最简单的使用,其实咱们能够经过transient关键字控制哪些字段不被序列化,还有静态成员不属于对象,不会参与序列化过程,还能够手动控制Serializable对象的序列化和反序列化过程,还关于Serializable更多使用方式能够看Java对象表示方式1:序列化、反序列化和transient关键字的做用

1.二、Parcelable

Parcelable是Android提供的一个接口,一个对象只要实现了这个接口,就能够实现序列化,以下:

public class User implements Parcelable {

    private final String name;
    private final String password;

    public User(String name, String password) {
        this.name = name;
        this.password = password;
    }
    
    public String getName() {
        return name == null ? "" : name;
    }

    public String getPassword() {
        return password == null ? "" : password;
    }

    //下面就是实现Parcelable接口须要实现的方法
    
    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(this.name);
        dest.writeString(this.password);
    }

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

        @Override
        public User[] newArray(int size) {
            return new User[size];
        }
    };
    
    protected User(Parcel in) {
        this.name = in.readString();
        this.password = in.readString();
    }
}
复制代码

User中须要序列化的字段都会包装进Parcel中,当反序列化时,从Parcel中读取数据,Parcel内部包装了可序列化的数据,能够在进程间传输,其中describeContents方法用来完成内容描述,通常返回0,writeToParcel方法用来实现序列化,CREATOR对象的内部方法用来实现反序列化,其中createFromParcel方法用来从序列化后的对象中建立原始对象,newArray方法用来建立指定长度的原始对象数组。

Parcelable的用法相比Serializable复杂了一点,若是每次须要序列化一个对象,都要写这么多重复样本代码,会有点累,这里我推荐一个插件android-parcelable-intellij-plugin,专门用于自动生成这些重复样本代码。

1.三、Serializable和Parcelable的区别

Serializable和Parcelable均可以实现序列化,用于进程间的数据传递,它们之间有什么区别呢?在编码上,Serializable使用简单,而Parcelable稍显复杂;在效率上,Serializable的开销很大,由于它只是一个空接口,咱们无需实现任何方法,Java便会对这个对象进行序列化操做,这是由于java会经过反射建立所需方法,致使序列化的过程较慢,而Parcelable的效率高,它的速度比Serializable快十倍,经过把一个完整的对象进行分解,而分解后的每一部分都是Intent所支持的数据类型,这样也就实现了传递对象的功能;在使用场景上,Parcelable主要用在内存序列化上,它不能使用在要将数据序列化在磁盘上的状况,由于在外界有变化的状况下,Parcelable不能很好的保证数据的持续性,因此若是须要序列化到磁盘或经过网络传输建议使用Serializable,尽管它的效率低点。

综上所述,在Android开发中,若是有序列化的要求,咱们多点考虑使用Parcelable,毕竟它是Android自带的,虽然它使用稍显复杂,可是有插件的帮助,加上它的效率高,就略胜Serializable了。

二、开启多进程模式

既然是进程间通讯,就必定会涉及到两个或两个进程以上,咱们知道,在Android中,启动一个应用就表明启动了一个进程,但其实除了启动多个应用外,其实在一个应用中,也能够存在多个进程,只要你为四大组件在AndroidMenifest中指定“android:process”属性就行,好比我但愿ClientActivity启动的时候,运行在独立的进程,我就会在AndroidMenifest中这样写,以下:

<activity android:name=".MainActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<activity android:name=".ClientActivity" android:process="com.example.aidltest.client"/>
复制代码

能够看到我只是简单的ClientActivity指定了“android:process”属性,这样ClientActivity在启动的时候就会运行在进程名为com.example.aidltest.client的独立进程上,而MainActivity启动时,就会运行在默认进程上,默认进程的进程名为当前包名即com.example.aidltest。

须要注意的是,此时这两个Activity虽然在同一个应用,可是却不在同一个进程,因此它们是不共享内存空间的,Android会为每个进程分配一个独立的虚拟机,不一样的虚拟机在内存分配上有不一样的地址空间,因此MainActivity和ClientActivity的内存地址空间不同,它们要访问对方的数据的时候,都要经过进程间通讯的方式来进行。

接下来为了方便后面的讲解,我把ClientActivity指定为主活动,把MainActivity删除,再建立一个Server,并为它指定process属性,以下:

<activity android:name=".ClientActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />

        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

<service android:name=".RemoteService" android:enabled="true" android:exported="true" android:process="com.example.aidltest.remote"/>
复制代码

ClientActivity启动时会运行在默认的进程上,RemoteService启动时会运行在名为com.example.aidltest.remote的进程上,它们在本文分别表明为本地客户端和远程服务端。

使用AIDL

前面花了一点篇幅来说解序列化和其余进程间通讯的方式,主要是让你们有个心理准备,下面进入正文:

一、AIDL是什么

它全称是Android Interface Definition Language,即Android接口定义语言,为了使其余的进程也能够访问本进程提供的服务,Android使用AIDL来公开服务的接口,它里面定义了本进程能够为其余进程提供什么服务,即定义了一些方法,其余进程就能够经过RPC(远程调用)来调用这些方法,从而得到服务,其中提供服务的进程称为服务端,获取服务的进程称为客户端。

二、AIDL接口的建立

AIDL接口用来暴露服务点提供给客户端的方法,新建一个AIDL接口文件,只须要在你的项目中 点击包名 -> 右键 -> new -> AIDL -> Aidl.file,而后输入AIDL接口名称,这里我输入了IUserManager,而后点击Finish,就会在你的main目录下建立了一个aidl文件夹,aidl文件夹里的包名和java文件夹里的包名相同,里面用来存放AIDL接口文件,以下:

在里面你会发现你刚刚建立的AIDL接口IUserManager,点进去,以下:

// IUserManager.aidl
package com.example.aidltest;

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

interface IUserManager {
    /** * 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支持的一些数据类型(int、long、boolean、float、double、String),除了这些,AIDL还支持其余的基本数据类型、ArrayList(里面的每一个元素都要被AIDL支持)、HashMap(里面的每一个元素都要被AIDL支持)、实现了Parcelable接口的对象和AIDL接口自己,还有AIDL接口中只支持声明方法,不支持声明静态常量。

其中若是要在AIDL接口文件中使用AIDL对象,必须显式的 import 进来,即便它们在同一个包内,还有若是在AIDL接口文件用到了Parcelable对象,必须新建一个和它同名的AIDL文件,并在其中声明它为parcelable类型,接下来我要使用User这个Parcelable对象,因此我要在aidl文件夹下新建一个和他同名的AIDL文件,以下:

而后在User.aidl中添加以下内容:

// User.aidl
package com.example.aidltest;

parcelable User;
复制代码

在里面,我声明了User.java这个对象为parcelable类型,接下来把IUserManager中的basicTypes方法删除,添加一个根据用户姓名得到用户信息的方法,以下:

// IUserManager.aidl
package com.example.aidltest;

import com.example.adiltest.User;

interface IUserManager {

   User getUser(String name);
}
复制代码

在里面我显示的 import 了User这个AIDL文件,即便它们在同一个包内,并声明了一个getUser方法,这个方法将会在服务端实现,而后在客户端调用(RPC)。

三、根据AIDL接口生成 对应的Binder类

有了AIDL接口后咱们须要根据AIDL接口生成客户端和服务端对应的Binder类,有两种方式生成,一种是经过SDK自动生成,另一种是咱们本身手动编码实现,其中可以进行手动编码实现的前提是基于对SDK自动生成的各类Binder类的充分理解,下面咱们先来介绍SDK自动生成的Binder类。

3.一、SDK自动生成

咱们在AS导航栏 Build -> ReBuild Project,SDK就会替咱们在 app\build\generated\aidl_source_output_dir\debug\compileDebugAidl\out\包名 下生成一个IUserManager.java,它就是根据IUserManager.aidl文件生成的,里面没有缩进,因此看起来不习惯,使用快捷键ctrl+alt+L,格式化一下代码,以下:

//IUserManager.java

/** * 一、IUserManager接口,getUser方法定义在其中 **/
public interface IUserManager extends android.os.IInterface {
   
    /** * 一、抽象类Stub,须要在远程服务端实现 */
    public static abstract class Stub extends android.os.Binder implements com.example.aidltest.IUserManager {
        private static final java.lang.String DESCRIPTOR = "com.example.aidltest.IUserManager";

        public Stub() {
            this.attachInterface(this, DESCRIPTOR);
        }
        
        public static com.example.aidltest.IUserManager asInterface(android.os.IBinder obj) {
            if ((obj == null)) {
                return null;
            }
            android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (((iin != null) && (iin instanceof com.example.aidltest.IUserManager))) {
                return ((com.example.aidltest.IUserManager) iin);
            }
            return new com.example.aidltest.IUserManager.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 {
            java.lang.String descriptor = DESCRIPTOR;
            switch (code) {
                case INTERFACE_TRANSACTION: {
                    reply.writeString(descriptor);
                    return true;
                }
                case TRANSACTION_getUser: {//case TRANSACTION_getUser分支
                    data.enforceInterface(descriptor);
                    java.lang.String _arg0;
                    _arg0 = data.readString();
                    //调用getUser方法的具体实现
                    com.example.aidltest.User _result = this.getUser(_arg0);
                    reply.writeNoException();
                    if ((_result != null)) {
                        reply.writeInt(1);
                        _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                    } else {
                        reply.writeInt(0);
                    }
                    return true;
                }
                default: {
                    return super.onTransact(code, data, reply, flags);
                }
            }
        }

        /** * 二、代理类Proxy,客户端使用 */
        private static class Proxy implements com.example.aidltest.IUserManager {
            
            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 com.example.aidltest.User getUser(java.lang.String name) throws android.os.RemoteException {
                android.os.Parcel _data = android.os.Parcel.obtain();
                android.os.Parcel _reply = android.os.Parcel.obtain();
                com.example.aidltest.User _result;
                try {
                    _data.writeInterfaceToken(DESCRIPTOR);
                    _data.writeString(name);
                    //传进了TRANSACTION_getUser字段
                    mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);
                    _reply.readException();
                    if ((0 != _reply.readInt())) {
                        _result = com.example.aidltest.User.CREATOR.createFromParcel(_reply);
                    } else {
                        _result = null;
                    }
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
                return _result;
            }
        }

        static final int TRANSACTION_getUser = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
    }
    
 	public com.example.aidltest.User getUser(java.lang.String name) throws android.os.RemoteException;
}

复制代码

这个文件有3个主要的类:

一、IUserManager接口

它声明了IUserManager.aidl定义的getUser方法,继承自IInterface。

二、IUserManager.Stub抽象类

IUserManager的静态内部类,它继承自Binder,说明它是一个 Binder 本地对象(Binder下面介绍),它虽然实现了IUserManager接口,可是它继续声明为一个抽象类,并无实现IUserManager接口中的getUser方法,代表子类须要实现getUser方法,返回具体的User信息,而服务端将会实现这个Stub抽象类。

**三、IUserManager.Stub.Proxy代理类 **

IUserManager.stub的静态内部类,它实现了IUserManager接口,而且实现了getUser方法,可是里面只是把数据装进data这个Parcel对象,经过mRemote的transact方法发送给服务端,接着用reply这个Parcel对象等待服务端数据的返回,这一切都是经过mRemote这个IBinder对象进行,mRemote表明着Binder对象的本地代理(IBinder下面介绍),mRemote会经过Binder驱动来完成与远程服务端的Stub的通讯。

能够看到Stub类和Stub.Proxy类都实现了IUserManager接口,这就是一个典型的代理模式,它们的getUser方法有着不一样的实现,Stub类它将会在远程的服务端完成getUser方法的具体实现,而Stub.Proxy类是本地客户端的一个代理类,它已经替咱们默认的实现了getUser方法,该方法里面经过mRemote这个Binder引用的transact方法把请求经过驱动发送给服务端,咱们注意到mRemote发送请求时还传进了TRANSACTION_getUser这个表明着getUser方法的标识名,这表示客户端告诉服务端我要调用getUser这个方法,当驱动把请求转发给服务端后,服务端的Stub类的onTransact方法就会回调,它里面有一个switch语句,根据code来调用不一样的方法,这时它就会走到case TRANSACTION_getUser这个分支,而后调用getUser方法的在服务端的具体实现,若是有返回值的话,还会经过reply返回给客户端,这样就经过Binder驱动完成了一次远程方法调用(RPC)。

这里要注意的是客户端经过mRemote的transact方法把请求发送给客户端以后,这时会阻塞UI线程等待服务端的返回,而服务端的onTransact方法回调时,服务端的getUser方法会被回调,这时服务端的getUser方法是运行在服务端Binder线程池中,因此若是此时有UI操做须要回到UI线程再进行UI操做。

咱们还注意到IUserManager接口继承了IInterface,IUserManager.Stub继承自Binder,它们是干什么的?咱们来认识一下:

IInterface

这是一个接口,用来表示服务端提供了哪些服务,若是服务端须要暴露调用服务的方法给客户端使用,就必定要继承这个接口,它里面有个asBinder方法,用于返回当前的Binder对象。

IBinder

这时一个跨进程通讯的Base接口,它声明了跨进程通讯须要实现的一系列抽象方法,实现了这个接口就说明能够进行跨进程通讯,Binder和BinderProxy都继承了这个接口。

Binder

表明的是 Binder 本地对象(Binder实体),它继承自IBinder,全部本地对象都要继承Binder,Binder中有一个内部类BinderProxy,它也继承自IBinder,它表明着Binder对象的本地代理(Binder引用),Binder实体只存在于服务端,而Binder引用则遍及于各个客户端。

接下来咱们动手实践一下,首先在服务端RemoteService中,咱们要这样作:

public class RemoteService extends Service {

    //一、实现Stub类中的getUser方法
    private IUserManager.Stub mBinder = new IUserManager.Stub() {
        @Override
        public User getUser(String name) throws RemoteException {
            //这里只是简单的返回了一个用户名为name,密码为123456的用户实例
            return new User(name, "123456");
        }
    };

    public RemoteService() {
    }

    @Override
    public IBinder onBind(Intent intent) {
        //二、在onBinder方法中返回Stub类的实现,Stub类继承自Binder,Binder实现了IBinder,这样返回是没问题的
        return mBinder;
    }
}
复制代码

在服务端咱们须要实现Stub类中的getUser方法,而后在onBinder方法中返回Stub类的实现,这样客户端绑定服务时就会收到这个Stub类的Binder引用。

而后在客户端ClientActivity中,咱们要这样作:

public class ClientActivity extends AppCompatActivity {

    private static final String TAG = ClientActivity.class.getSimpleName();

    //一、建立ServiceConnection
    private ServiceConnection mServiceConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            Log.d(TAG, "onServiceConnected, 与服务端链接成功!");
            
            //把服务端返回的Binder引用,经过Stub.asInterface方法包装成本地代理类IUserManager.Stub.Proxy,Proxy类实现了IUserManager,因此这样写是没问题的
            IUserManager userManager = IUserManager.Stub.asInterface(service);
            
            try {
                //经过本地代理对象远程调用服务端的方法
                User user = userManager.getUser("rain");
                
                Log.d(TAG, "onServiceConnected,向服务端获取用户信息成功,User = ["
                        + "name = " + user.getName()
                        + "password = " + user.getPassword());
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            Log.d(TAG, "onServiceDisconnected, 与服务端断开链接");

        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //二、启动并经过ServiceConnection绑定远程服务
        bindService(
                new Intent(this, RemoteService.class),
                mServiceConnection,
                Context.BIND_AUTO_CREATE
        );
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //三、解绑服务
        unbindService(mServiceConnection);
    }
}
复制代码

在服务端咱们首先要建立ServiceConnection,而后经过ServiceConnection来绑定RemoteService,在成功绑定后,ServiceConnection的onServiceConnected方法就会回调,它的第二个输入参数就是RemoteService在onBind方法返回的Stub类的Binder引用,咱们拿到这个引用后,就能够经过经过Stub.asInterface方法转换为本地代理类Stub.Proxy,而后调用它的getUser方法,Proxy的getUser方法会远程调用RemoteService的getUser方法,方法返回后,在log中打印出User的信息,最后,活动结束,咱们记得解绑服务,这个过程和上面介绍的一次RPC过程是同样的。

咱们发现完成一次进程间的通讯是很是的简单,这就好像只是简单的调用一个对象的方法,但其实这都得益于Binder在底层为咱们作了更多工做,咱们上层使用得有多简单,它得底层实现就有多复杂,上面的一次进程间通讯,能够简单的用下图表示:

其中ServiceManager就是根据Binder的名字查找Binder引用并返回,若是你对Binder的通讯架构有必定的了解,理解这个就不难,对象转换就是完成Binder实体 -> Binder引用,Binder引用 -> Binder实体的转换,在java层继承自Binder的都表明Binder本地对象,即Binder实体,而Binder类的内部类BinderProxy就表明着Binder对象的本地代理,即Binder引用,这两个类都继承自IBinder, 于是都具备跨进程传输的能力,在跨越进程的时候,Binder 驱动会自动完成这两个对象的转换。

咱们重点讲一下图中的第3步:将Binder引用赋值给Proxy的mRemote字段,Proxy就是前面介绍的Stub.Proxy,因此接着咱们看看**IUserManager.Stub.asInterface(IBinder)**方法是如何把服务端返回的Binder引用赋值给本地的代理类Proxy的mRemote字段,asInterface方法以下:

//IUserManager.Stub.java
public static com.example.aidltest.IUserManager asInterface(android.os.IBinder obj) {
    if ((obj == null)) {
        return null;
    }
    //根据DESCRIPTOR调用IBinder的queryLocalInterface方法
    android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
    if (((iin != null) && (iin instanceof com.example.aidltest.IUserManager))) {
        return ((com.example.aidltest.IUserManager) iin);
    }
    return new com.example.aidltest.IUserManager.Stub.Proxy(obj);
}

//Binder
public @Nullable IInterface queryLocalInterface(@NonNull String descriptor) {
    if (mDescriptor != null && mDescriptor.equals(descriptor)) {
        //mOwner等于Stub类实例
        return mOwner;
    }
    return null;
}

//Binder#BinderProxy
public IInterface queryLocalInterface(String descriptor) {
    return null;
}
复制代码

能够发现它里面根据DESCRIPTOR标识调用IBinder的queryLocalInterface方法在查找一些什么,DESCRIPTOR是什么?DESCRIPTOR是Binder实体的惟一标识,通常用当前的Binder的类名表示,它定义在Stub类中,如本文的Stub的 DESCRIPTOR = "com.example.aidltest.IUserManager",前面已经讲过Binder和BinderProxy都继承自IBinder,因此它们的queryLocalInterface有不一样的实现,咱们看到BinderProxy的直接返回null;而Binder的须要和本身的DESCRIPTOR比较,若是相同就返回mOwner,不然返回null,其中mOwner就等于Stub类实例,在Stub类构造的时候赋值,以下:

public static abstract class Stub extends android.os.Binder implements com.example.aidltest.IUserManager {
    
    private static final java.lang.String DESCRIPTOR = "com.example.aidltest.IUserManager";

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public void attachInterface(@Nullable IInterface owner, @Nullable String descriptor) {
        mOwner = owner;
        mDescriptor = descriptor;
    }
    
    //...
}

复制代码

这说明了若是queryLocalInterface的返回不为空,iin != null,表示obj是Binder实体(Stub的子类),客户端和服务端在同一个进程,asInterface方法返回的就是Binder实体;若是queryLocalInterface的返回为空,iin == nul,表示obj其实是Binder引用,客户端和服务端在不一样的进程,asInterface构造一个Proxy对象返回,并把Binder引用经过构造传了进去,咱们看Proxy的构造函数,以下:

private static class Proxy implements com.example.aidltest.IUserManager {
    private android.os.IBinder mRemote;

    Proxy(android.os.IBinder remote) {
        mRemote = remote;
    }
    
    //...
}
复制代码

能够看到,传进去的Binder引用赋值给了mRemote字段,因此Proxy中的mRemote就是Binder引用,客户端就是经过这个mRemote来和服务端通讯。

也就是说,若是在同一个进程,asInterface返回的是Stub的实现类,由于不存在跨进程调用,直接调用该方法就行,若是在不一样进程,asInterface返回的是Proxy对象,客户端调用Proxy中的同名方法,经过mRemote的transact方法挂起当前线程等待服务端返回,服务端收到请求后响应返回数据。

到这里,相信你们在SDK把帮助下已经会使用AIDL来完成简单的进程间通讯,接下来经过手动编码实现。

3.二、手动编码实现

咱们发现使用AIDL系统会自动的帮咱们生成上述代码,是为了方便咱们的开发,系统根据AIDL文件生成的java文件格式是固定,咱们彻底能够抛开AIDL直接手写对应的Binder类,下面咱们本着单一原则把本来IUserManager.java的里面的Stub类和Stub类中的Proxy类独立出来,因此咱们总共要写3个类,分别是:IUserManager、Stub、Proxy。

一、声明一个继承自IInterface的接口

声明一个继承自IInterface的接口,在里面定义咱们想要让客户端调用的方法,以下:

public interface IUserManager extends IInterface {
    User getUser(String name);
}
复制代码

二、声明服务端的Stub类

Stub类须要继承Binder,代表它是一个Binder本地对象,它还须要实现IUserManager接口,可是继续声明为抽象类,不须要实现IUserManager的getUser方法,接着咱们作如下几步:

一、在里面定义一个字符串DESCRIPTOR,表示Binder实体的惟一标识,用当前的Stub类的类名表示,并把它的可见修饰符改成public,待会在Proxy须要用到.

二、在构造函数中把this 和 DESCRIPTOR字符串 attach 给父类Binder中的mOwner和mDescriptor字段.

三、定义一个TRANSACTION_getUser整型数值表明着getUser方法的标识名,赋值格式照抄自动生成的Stub类的TRANSACTION_getUser.

四、定义一个asInterface静态方法,里面的内容实现照抄自动生成的Stub类的asInterface方法,须要注意里面的IUserManager接口须要换成咱们刚刚定义的IUserManager接口.

五、最后重写IInterface的asBinder方法和Binder的onTransact方法,里面的内容实现照抄自动生成的Stub类的asBinder和onTransact方法.

最终这个Stub类以下:

public abstract class Stub extends Binder implements IUserManager {

    public static final String DESCRIPTOR = "com.example.aidltest.Stub";//这里改为com.example.aidltest.Stub

    static final int TRANSACTION_getUser = (IBinder.FIRST_CALL_TRANSACTION + 0);

    public Stub() {
        this.attachInterface(this, DESCRIPTOR);
    }

    public static IUserManager asInterface(android.os.IBinder obj) {//导入咱们自定义的IUserManager
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof IUserManager))) {
            return (IUserManager) iin;
        }
        return new 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 {
        java.lang.String descriptor = DESCRIPTOR;
        switch (code) {
            case INTERFACE_TRANSACTION: {
                reply.writeString(descriptor);
                return true;
            }
            case TRANSACTION_getUser: {
                data.enforceInterface(descriptor);
                java.lang.String _arg0;
                _arg0 = data.readString();
                com.example.aidltest.User _result = this.getUser(_arg0);
                reply.writeNoException();
                if ((_result != null)) {
                    reply.writeInt(1);
                    _result.writeToParcel(reply, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                } else {
                    reply.writeInt(0);
                }
                return true;
            }
            default: {
                return super.onTransact(code, data, reply, flags);
            }
        }
    }
}
复制代码

三、声明客户端的Proxy类

Proxy类只须要实现IUserManager接口,而且实现IUserManager中的getUser方法,接着咱们作如下几步:

一、定义一个IBinder类型的mRemote字段,并在构造函数中赋值.

二、实现IUserManager中的getUser方法和IInterface的asBinder方法,里面的内容实现照抄自动生成的Stub.Proxy的getUser方法和asBinder方法,须要注意getUser中的一些字段须要导入咱们刚刚在Stub类中定义的字段.

最终这个Proxy类以下:

public class Proxy implements IUserManager {

    private IBinder mRemote;

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

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

    @Override
    public com.example.aidltest.User getUser(java.lang.String name) throws android.os.RemoteException {
        android.os.Parcel _data = android.os.Parcel.obtain();
        android.os.Parcel _reply = android.os.Parcel.obtain();
        com.example.aidltest.User _result;
        try {
            _data.writeInterfaceToken(Stub.DESCRIPTOR);//这里引用咱们本身写的Stub的DESCRIPTOR
            _data.writeString(name);
            mRemote.transact(Stub.TRANSACTION_getUser, _data, _reply, 0);//这里引用咱们本身写的Stub的TRANSACTION_getUser
            _reply.readException();
            if ((0 != _reply.readInt())) {
                _result = com.example.aidltest.User.CREATOR.createFromParcel(_reply);
            } else {
                _result = null;
            }
        } finally {
            _reply.recycle();
            _data.recycle();
        }
        return _result;
    }

}
复制代码

最终整个工程目录以下:

四、使用

使用就很简单了,只须要把上面自动生成的IUserManager、IUserManager.Stub、IUserManager.Stub.Proxy替换成咱们本身写的IUserManager、Stub、Proxy就行,就再也不贴代码了,输出以下:

学完AIDL能干什么

日常在Android中应用到进程间通讯的场景很是的少,但这并非说AIDL没有用,一个最直观的应用就是在阅读Android系统源码的时候,例如Activity的启动流程:

Activity的启动流程(1)

Activity的启动流程(2)

在android8.0以后Activity的有关进程间的通讯都是经过AIDL来实现,Android根据IActivityManager.aidl文件来生成进程间通讯所需的Binder类,如ApplicationThread在AMS的本地代理,AMS在ActivityThread的本地代理,当咱们经过startActiivty发起Activity的启动请求时,ActivityThread就会经过AMS本地代理调用AMS的相应方法,当Actiivty在AMS准备好后,AMS就会经过ActivityThread本地代理回调应用进程的Activity的生命周期方法,这里就不在多述了,这个过程以下:

主要是经过这个例子说明,学习完AIDL后,可以帮助咱们更好的理解系统源码有关跨进程的一些术语,类等,经过AIDL也能更好的加深咱们对Android进程间通讯的原理的理解,也掌握了一种进程间通讯的方式。

结语

本文简单的介绍了一下Android几种进程间通讯的方式,而后经过SDK自动生成AIDL代码来理解了一下生成的代码中各个类的做用和关系,还根据自动生成AIDL代码来手动实现了一遍简单的跨进程通讯,加深理解,掌握了一些基础AIDL知识,可能会有些不全面,可是足够基本使用,想要了解更全面的AIDL知识,最好的途径仍是参阅官方文档:Android 接口定义语言 (AIDL)

本文源码地址

参考资料:

Android系统中Parcelable和Serializable的区别

关于Binder,做为应用开发者你须要知道的所有

相关文章
相关标签/搜索