AIDL使用以及原理分析

AIDL使用以及IPC原理分析(进程间通信)

概要

为了大家能够更好的理解android的进程间通信原理,以下将会从以下几个方面讲解跨进程通讯信:

  1. 必要了解的概念
  2. 为什么要使用aidl进程间通信
  3. 可能遇到的问题以及解决办法
  4. aidl的使用,通过android提供的aidl实现一个进程间通信
  5. 不使用aidl,手动编写Binder实现进程间通信
  6. 分析aidl的原理,梳理andriod进程间通信相关知识

1.必要了解的概念

a.IPC

IPC是Inner-Process Communication,就是进程间通信。

b.AIDL

AUDL是Android Interface Define Language 安卓接口语言缩写。

c.Binder

Binder是android中负责进程间通信的驱动类,Binder内部设计十分复杂这里我们暂不做深入研究,这里我们只需要了解它是负责进程间通信的类即可。

d.Proxy代理模式

如果你不是很了解代理模式,可以去这里看看。
Proxy_Pattern

2.WHY?

a. 某些情况下远端的服务更适合运算或者更适合执行耗时操作,这时候我们会使用aidl请求远程服务;
b. android对单个应用的内存限制,当有需求需要突破这个限制的时候我们需要另启进程扩大内存。

实际使用情况还有很多,笔者遇到的情况还不是很多这里就不意义列举了,反正aidl是一种很有效的IPC通信方式。

3.可能遇到的问题

我们都知道在android中一个应用就对应一个linux进程,或者说默认情况下所有的组件都是在同一个进程下的;我们也可以将不同的组件放在不同的进程中,详情请查看我的另外一篇文章Multi_Process_Component,这样应用就不止一个进程了,按照一个进程对应一个虚拟机,也就是说我们应用不止一个虚拟机了。可能出现的问题我们来举个栗子:

假设你的代码里有一个单例,DemoSingletion;虚拟机1启动时创建了这个单例,你在虚拟机中任何一个线程中使用都只有它一个对象,线程同步问题可以添加线程锁解决。那么问题来了,虚拟机2启动的时候还会再创建这个单例吗?如果不创建的话和虚拟机1使用的是同一个单例吗?

实际情况是每个虚拟机启动的时候都会创建各自的单例,他们是不同的对象,在不同的地址空间上。那么问题又来了,两个虚拟机操作的是不同的对象那么这个DemoSingletion怎么同步呢?无论是添加线程锁还是对象锁我们都无法做到同步,究其原因就是操作的是不同的对象。这就是多进程带来的问题之一,接下来我们列举会出现的问题并说明如何解决这些问题。

  1. 单例模式完全失效
  2. 静态变量无法同步

4.AIDL的使用

注:由于基于eclipse的adt过于老旧这里不再讲解操作,请使用android studio完成以下操作。

· 使用AIDL文件

a.新建aidl文件

在你想要创建aidl的包下新建aidl文件(这里我们命名为IDataManager),aidl文件的语法与java类似,默认生成的aidl会有一个demo方法

void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat,double aDouble, String aString);

系统生成的basicTypes这个demo方法告诉我们能够传递那些类型的数据。

b.添加自定义方法

// 无论应用的类是否和aidl文件在同一包下,都需要显示import
import org.github.lion.aidl_demo.Data;
// Declare any non-default types here with import statements

interface IDataManager {
    /**
     * 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);

    int getDataTypeCount();

    List<Data> getData();

    String getUrlContent(String url);
}

List<Data> getData()这个方法中使用了自定义的数据类型,虽然我们在文件开头写了import但是还是无法通过编译,我们需要在sdk的platform下修改framework.aidl,完整路径如下:~/platforms/android-xx/framework.aidl,加入我们自己添加的类名即可:

// user define aidl parcelable data
parcelable org.github.lion.aidl_demo.Data;

这个路径实际上是系统定义的Parcelable类~/platforms/android-xx/framework.aidl,这里我们不建议修改这个文件,另一种方式是aidl文件,定义如下:

// Data.aidl Data 类的完整包名为 org.github.lion.aidl_demo.Data,我们定义的aidl文件如下即可。
package org.github.lion.aidl_demo;
parcelable Data;

Data需实现Parcelable接口。
以下是android studio的默认实现。

/**
 * Created by lion on 2016/10/11.
 * 要通过Bundle传递的数据需要实现Parcelable接口,
 * 一旦你实现了这个接口android studio会提示你帮
 * 你快速实现带有Parcel的构造函数。
 */
public class Data implements Parcelable {

    ...

    protected Data(Parcel in) {
        ...
    }

    public static final Creator<Data> CREATOR = new Creator<Data>() {
        @Override
        public Data createFromParcel(Parcel in) {
            return new Data(in);
        }

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

c. 编译aidl文件

到这里aidl的编写就完成了,我们build下工程,编译器会自动生成IDataManager.java文件。
该文件在工程的~/app/build/generated/source/aidl/debug/<package>/IDataManager.java,这里我们先不讲解生成的这个类,先看下如何使用aidl。

d. 添加Service类(远端服务)

添加一个Service命名为DataManagerService我们在DataManagerService中实现一个静态的IDataManager.Stub的类

private static final IDataManager.Stub mBinder = new IDataManager.Stub() {

    @Override
    public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, String aString) throws RemoteException {
    }

    @Override
    public int getDataTypeCount() throws RemoteException {
        // todo return some data
        return 0;
    }

    @Override
    public List<Data> getData() throws RemoteException {
        // todo return some data
        return null;
    }

    @Override
    public String getUrlContent(String url) throws RemoteException {
        // todo return some data
        return null;
    }
};

onBind方法中返回这个Binder,这样当我们调用Activity的bindService方法的时候就能返回这个binder对象了。

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

e.绑定服务并测试夸进程通信

在你需要调用的Activity中添加如下代码:

/**
 * data manager service 的远程引用
 */
private IDataManager dataManagerService = null;

/**
 * 创建Service Connection用于监听service链接与断开链接
 */
private ServiceConnection dataServiceConnection = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        dataManagerService = IDataManager.Stub.asInterface(service);
    }

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

当你的Activity启动时绑定远程服务

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    bindService(new Intent(this, DataManagerService.class), dataServiceConnection,
            Context.BIND_AUTO_CREATE);
}

接下来我们编写测试代码,在button的回调函数中我们编写如下测试代码:

public void callService(View view) {
    try {
        System.out.println(dataManagerService.getDataTypeCount());
        
        StringBuilder sb = new StringBuilder();
        for (Data data : dataManagerService.getData()) {
            System.out.println(data.toString());
            sb.append(data.toString()).append("\n");
        }
        textData.setText(sb.toString());

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println(dataManagerService.getUrlContent("http://www.baidu.com"));
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

f.运行查看结果

·自己实现Binder

上面我们展示了如何使用AIDL文件实现进程间通信,为了能够更好的理解进程间通信机制接下来将会展示如何手动编写一个Binder实现IPC。

aidl生成类分析

将Android Studio切换到项目视图,找到如下文件:


Android Studio aidl 自动生成类

我们将这个接口文件简化以下,看看系统多给我们做了些什么。

public interface IDataManager extends android.os.IInterface {

    /**
     * Local-side IPC implementation stub class.
     */
    public static abstract class Stub extends android.os.Binder implements org.github.lion.aidl_demo.IDataManager {

        private static class Proxy implements org.github.lion.aidl_demo.IDataManager {}
    }
}

IDataManager

这个是我们定义的aidl接口,这个接口里面就要定义我们需要的要成服务能力的接口;

IDataManager.Stub

这个是一个继承自Binder并且实现了IDataManager的抽象类;

IDataManager.Stub.Proxy

这个是一个私有内部类,实现了IDataManager;

我们知道Binder是Android中的IPC通信驱动,从类结构我们就可以看出最终的实际功能类是IDataManager.Stub.Proxy。具体的类方法我们暂时不做分析,接下来我们不使用aidl文件自己实现一个Binder驱动类,写的过程中我们细细来分析各个函数的功能。

5.自己实现Binder驱动IPC通信

定义公共接口

从上面aidl生成的类我们看出需要实现IPC通信需要实现IInterface接口,并且继承Binder类从中间驱动。所以首先我们先定义公共接口继承IInterface接口。

//IDataManager.java
public interface IDataManager2 extends IInterface {

    // 返回值为基本数据类型,定义接口时不需要做特殊处理
    int getDataCount() throws RemoteException;

    // 自定义的返回数据类型需要实现Parcelable接口,进程间通信不能直接共享内存,需要将对象持久化。
    // 所以自定义的类需要实现Parcelable接口
    List<Data2> getData() throws RemoteException;
}


/**
 * Data2.java
 * Created by lion on 2016/10/11.
 * 要通过Bundle传递的数据需要实现Parcelable接口,
 * 一旦你实现了这个接口android studio会提示你帮
 * 你快速实现带有Parcel的构造函数。
 */
public class Data2 implements Parcelable {

    private int id;
    private String content;

    public Data2() {
    }

    protected Data2(Parcel in) {
        id = in.readInt();
        content = in.readString();
    }

    public static final Creator<Data2> CREATOR = new Creator<Data2>() {
        @Override
        public Data2 createFromParcel(Parcel in) {
            return new Data2(in);
        }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }

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

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeInt(id);
        dest.writeString(content);
    }

    @Override
    public String toString() {
        return "id = " + id + " content = " + content;
    }
}

继承Binder并实现IDataManager2接口的类作为Binder的本体。
为了让代码逻辑更加清晰,这回我们的Binder类不再写成内部类。

public abstract class DataManagerNative extends Binder implements IDataManager2 {

    // Binder描述符,唯一标识符
    private static final String DESCRIPTOR = "com.github.onlynight.aidl_demo2.aidl.IDataManager2";

    // 每个方法对应的ID
    private static final int TRANSACTION_getDataCount = IBinder.FIRST_CALL_TRANSACTION;
    private static final int TRANSACTION_getData = IBinder.FIRST_CALL_TRANSACTION + 1;

    public DataManagerNative() {
        attachInterface(this, DESCRIPTOR);
    }

    /**
     * 将Binder转化为IInterface接口
     *
     * @param binder
     * @return
     */
    public static IDataManager2 asInterface(IBinder binder) {
        if (binder == null) {
            return null;
        }
        //同一进程内直接返回
        IInterface iin = binder.queryLocalInterface(DESCRIPTOR);
        if ((iin != null) && (iin instanceof IDataManager2)) {
            return (IDataManager2) iin;
        }

        //不在同一进程使用代理获取远程服务
        return new Proxy(binder);
    }

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

    /**
     * 我们查看Binder的源码就可以看出实际上transact方法真正的执行体
     * 是这个onTransact方法。
     *
     * @param code  服务器回掉的方法ID,每一个方法都有一个唯一id,
     *              这样方法回调时可通过id判断回调的方法。
     * @param data  输入的参数,传递给服务端的参数
     * @param reply 输出的参数,服务器返回的数据
     * @param flags 默认传入0
     * @return
     * @throws RemoteException 远端服务器无响应抛出该错误。
     */
    @Override
    protected boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
        switch (code) {
            case TRANSACTION_getDataCount: {
                data.enforceInterface(DESCRIPTOR);
                int _result = this.getDataCount();
                reply.writeNoException();
                reply.writeInt(_result);
                return true;
            }
            case TRANSACTION_getData: {
                data.enforceInterface(DESCRIPTOR);
                List<Data2> _result = this.getData();
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    /**
     * 代理类,调用transact方法。
     */
    private static class Proxy implements IDataManager2 {

        private IBinder remote;

        Proxy(IBinder remote) {
            this.remote = remote;
        }

        public String getInterfaceDescriptor() {
            return DESCRIPTOR;
        }

        @Override
        public int getDataCount() throws RemoteException {
            // 输入参数
            Parcel _data = Parcel.obtain();

            //输出参数
            Parcel _reply = Parcel.obtain();
            int _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);
                _reply.readException();
                _result = _reply.readInt();
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public List<Data2> getData() throws RemoteException {
            Parcel _data = Parcel.obtain();
            Parcel _reply = Parcel.obtain();
            List<Data2> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                remote.transact(TRANSACTION_getData, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(Data2.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }

        @Override
        public IBinder asBinder() {
            return remote;
        }
    }
}

DataManagerNative.DESCRIPTER

Binder描述符,唯一标识符,服务端和客户端都可以通过该ID定位到Binder实例。

DataManagerNative.TRANSACTION_XXX

自定义的IInterface方法的唯一标识符。

DataManagerNative.asInterface

将Binder转换为IInterface就可以直接调用我们自己定义的方法啦。

DataManagerNative.onTransact

根据不同的TRANSACTION_ID调用调用不同的方法。

DataManagerNative.Proxy

代理远端Binder,对外提供IDataManager2的功能。

DataManagerNative.Proxy.transact

想调用远端Binder的transact方法。

可以看到DataManagerNative是个抽象类,并没有实现IDataManager2中的方法。所以我们需要在实例化这个类的时候实现这些方法,这些操作都放到Service中去完成。

Service中Binder的实现我们和上一次使用同样的代码。

public class DataManagerService extends Service {

    private static List<Data2> data = new ArrayList<>();

    static {
        Data2 data1 = new Data2();
        data1.setId(1);
        data1.setContent("data1");
        data.add(data1);
        Data2 data2 = new Data2();
        data2.setId(2);
        data2.setContent("data2");
        data.add(data2);
        Data2 data3 = new Data2();
        data3.setId(3);
        data3.setContent("data3");
        data.add(data3);
        Data2 data4 = new Data2();
        data4.setId(4);
        data4.setContent("data4");
        data.add(data4);
        Data2 data5 = new Data2();
        data5.setId(5);
        data5.setContent("data5");
        data.add(data5);
    }

    // 可以看到我们在这里实现了这个Binder,这里才是这个Binder的本体。
    private static DataManagerNative binder = new DataManagerNative() {

        @Override
        public int getDataCount() throws RemoteException {
            return data.size();
        }

        @Override
        public List<Data2> getData() throws RemoteException {
            return data;
        }
    };

    public DataManagerService() {
    }

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

实际上我们自己修改过的类和编译器自动生成的类基本上是一样的,这里为了让大家有更加深刻的认识,我们将其手动实现一次。

6.AIDL原理分析

相信经过以上的分析大家应该有个大概的认识了,但是要动起手来应该会有很多地方卡住,接下来我们来个全面的分析,理清楚Binder的机制。

·运行原理图

首先我们先来看下原理图,让大家有个感性的认识:

Binder原理图

调用顺序是这样:Client->operate()->transact()->onTransact()->operation()->Server

我们能看到的源码执行顺序就是这样的,由于Binder内部结构很复杂,Binder内部的如何进行数据交换如何定位服务端方法我们这里不再介绍,感兴趣的朋友可以查看Android源码。

有几个比较有趣的地方我们单独拿出来说说。

首先是transact方法

public int getDataCount() throws RemoteException {
    // 输入参数
    Parcel _data = Parcel.obtain();

    //输出参数
    Parcel _reply = Parcel.obtain();
    int _result;
    try {
        _data.writeInterfaceToken(DESCRIPTOR);
        remote.transact(TRANSACTION_getDataCount, _data, _reply, 0);
        _reply.readException();
        _result = _reply.readInt();
    } finally {
        _reply.recycle();
        _data.recycle();
    }
    return _result;
}

其中_data是调用函数传入的参数,_reply是调用函数返回的结果。通过形参的方式返回在java中不常见,这里需要理解下,_reply即是函数执行完将结果赋值到这个引用中。我们只需要按照顺序read其中的结果即可_reply.readInt()

我们在看下transact方法的源码

/**
 * Default implementation rewinds the parcels and calls onTransact.  On
 * the remote side, transact calls into the binder to do the IPC.
 */
public final boolean transact(int code, Parcel data, Parcel reply,
        int flags) throws RemoteException {
    if (false) Log.v("Binder", "Transact: " + code + " to " + this);

    if (data != null) {
        data.setDataPosition(0);
    }
    boolean r = onTransact(code, data, reply, flags);
    if (reply != null) {
        reply.setDataPosition(0);
    }
    return r;
}

很明显onTransact才是真正的方法执行体,而onTransact方法调用了实现IDataManager2接口的类的实现方法;注意上面大的例子看起来稍微有些复杂,DataManagerNative是个抽象类它并没有实现IDataManager2中的方法,真正实现这些方法的DataManagerNative的实例在DataManagerService中,再结合上面的原理图,相信你现在已经很了解AIDL的通信机制了吧。

以上都是个人理解进行的分析,如果哪里有问题欢迎指出,最后希望这篇文章能够帮到你。

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

推荐阅读更多精彩内容