AIDL 的使用和代码分析

一.相关介绍

在 Android 系统中,进程间通信 (IPC) 是一种很重要的机制。IPC 产生的原因是某些情况下需要在两个进程之间进行一些数据的交换。而在深入学习 Android 的过程中难免会遇到 IPC 的相关问题,比如常见的有在自己的应用程序中读取手机联系人的信息,这就涉及到 IPC 了。因为自己的应用程序是一个进程,通讯录也是一个进程,只不过获取通讯录的数据信息是通过 Content Provider 的方式来实现的。

对于初学者来说,在一开始接触 IPC 时可能会摸不着头脑,因为网上很多博客在讲 Android IPC 时通常都是长篇大论,没有从例子着手。基于以上种种原因以及希望对 AIDL 有一个更深入的理解,本篇博文就诞生了。在 Android 系统中,IPC 的方式有很多种,比如有 Messenger 、AIDL 和 ContentProvider 等。我们今天就来讲讲其中的 AIDL ,AIDL 也是比较常见和经常使用的一种 IPC 方式。希望读者在看完本篇之后对于 AIDL 有一个比较深入的理解。

什么是 AIDL?

AIDL 的全称是 Android Interface Definition Language(即 Android 接口定义语言)。

AIDL是Binder的实例。

AIDL的使用实例:

我们来模拟一下需要进行 IPC 的情况,现在有客户端和服务端,客户端通过 AIDL 来和服务端进行 IPC 。我们假定现在客户端需要传一个 Person 类的对象给服务端,之后服务端回传给客户端一个 Person 类的集合。

1.服务端的相关代码

以下 Person.aidl 文件:

parcelable Person;

注意在 IPC 机制中传递的自定义对象需要序列化,所以要实现 Parcelable 接口。在 AIDL 文件中使用parcelable关键字声明。有了 Person.aidl 之后,我们就要创建 AIDL 接口了。

interface AddPersonInter {
List<Person> addPerson(in Person person);
}

在 IMyAidlInterface.aidl 里,主要声明一个用于添加 Person 对象的抽象方法。另外,需要注意以下几点:

1.Person 类需要手动去 import ,在 AIDL 文件中不能自动导包;

2.在addPerson方法里需要声明参数是 in 的,用来表示该参数是传入的。除了 in 之外,还有 out 和 inout ;

下面我们要创建一个 Service 用于和客户端进行 IPC 。这里还要把该 Service 运行在一个新的进程里。我们只要在 AndroidManifest.xml 中声明android:process=":remote"就行了。

public class MyService extends Service {
private static final String TAG = "MyService";

private List<Person> persons = new ArrayList<>();

public MyService() {
}

@Override
public IBinder onBind(Intent intent) {
    Log.e("aaaa", "binder绑定成功");
    return binder;
}

private final IBinder binder = new AddPersonInter.Stub() {
    @Override
    public List<Person> addPerson(Person person) throws RemoteException {
        synchronized (persons) {
            persons.add(person);
            Log.e("aaaaa", "服务端  name----" + person.getName() + "       age===" + person.getAge());
            return persons;
        }
    }
};
}

在上面的代码中我们可以看到在onBind(Intent intent)方法中返回了 mBinder ,而客户端正是通过这个 mBinder 来和服务端进行 IPC 的。mBinder 是 IMyAidlInterface.Stub 匿名类的对象,IMyAidlInterface.Stub 其实是一个抽象类,继承自 Binder ,实现addPerson方法。这里要注意以下,在addPerson的方法中需要将 persons 同步,这是因为在服务端 AIDL 是运行在 Binder 线程池中的,有可能会有多个客户端同时连接,这时候就需要同步以防止数据出错。

2.客户端的相关代码

客户端需要将服务端的aidl文件夹整体复制到客户端,并将用到到的java类Person.java复制到客户端,
注意包名一致,不然编译会报错。

public class Main2Activity extends AppCompatActivity {

TextView tv;
private AddPersonInter addPersonInter;


@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    tv=this.findViewById(R.id.tv);
    // 启动服务端的服务,并进行绑定
    Intent intent = new Intent();
    intent.setComponent(new ComponentName("com.seventeenok.test", "com.seventeenok.test.MyService"));
    bindService(intent, conn, Context.BIND_AUTO_CREATE);

    tv.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        List<Person> list = addPersonInter.addPerson(new Person("lizhi", 24));
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    });
}

private ServiceConnection conn = new ServiceConnection() {
    @Override
    public void onServiceConnected(ComponentName name, IBinder service) {
        addPersonInter = AddPersonInter.Stub.asInterface(service);
    }

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

AIDL 的流程基本上就是这样的。通过这个简单的例子,相信对于 AIDL 有了一个初步的了解。下面我们就要进行 AIDL 的代码分析。

3.项目的整体目录结构:
aaa.png

AIDL代码分析

工程中的 gen 目录下找到对应 AIDL 编译后的文件:

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

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

    /**
     * Cast an IBinder object into an com.seventeenok.test.AddPersonInter interface,
     * generating a proxy if needed.
     */
    public static com.seventeenok.test.AddPersonInter asInterface(android.os.IBinder obj) {
        if ((obj == null)) {
            return null;
        }
        android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
        if (((iin != null) && (iin instanceof com.seventeenok.test.AddPersonInter))) {
            return ((com.seventeenok.test.AddPersonInter) iin);
        }
        return new com.seventeenok.test.AddPersonInter.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_addPerson: {
                data.enforceInterface(DESCRIPTOR);
                com.seventeenok.test.Person _arg0;
                if ((0 != data.readInt())) {
                    _arg0 = com.seventeenok.test.Person.CREATOR.createFromParcel(data);
                } else {
                    _arg0 = null;
                }
                java.util.List<com.seventeenok.test.Person> _result = this.addPerson(_arg0);
                reply.writeNoException();
                reply.writeTypedList(_result);
                return true;
            }
        }
        return super.onTransact(code, data, reply, flags);
    }

    private static class Proxy implements com.seventeenok.test.AddPersonInter {
        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 java.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException {
            android.os.Parcel _data = android.os.Parcel.obtain();
            android.os.Parcel _reply = android.os.Parcel.obtain();
            java.util.List<com.seventeenok.test.Person> _result;
            try {
                _data.writeInterfaceToken(DESCRIPTOR);
                if ((person != null)) {
                    _data.writeInt(1);
                    person.writeToParcel(_data, 0);
                } else {
                    _data.writeInt(0);
                }
                mRemote.transact(Stub.TRANSACTION_addPerson, _data, _reply, 0);
                _reply.readException();
                _result = _reply.createTypedArrayList(com.seventeenok.test.Person.CREATOR);
            } finally {
                _reply.recycle();
                _data.recycle();
            }
            return _result;
        }
    }

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

public java.util.List<com.seventeenok.test.Person> addPerson(com.seventeenok.test.Person person) throws android.os.RemoteException;

}

可以看到编译后的 AddPersonInter.aidl 变成了一个接口,继承自 IInterface 。在 AddPersonInter接口中我们发现主要分成两部分结构:抽象类 Stub 和原来 aidl 中声明的addPerson方法。

重点在于 Stub 类,下面我们来分析一下。从 Stub 类中我们可以看到是继承自 Binder 并且实现了 AddPersonInter接口。 Stub 类的基本结构如下:

asInterface(android.os.IBinder obj)方法;

asBinder()方法;

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法;

静态类Proxy,主要方法是addPerson(com.seventeenok.test.Person person);

静态常量TRANSACTION_addPerson;

asInterface(android.os.IBinder obj)

我们先从asInterface(android.os.IBinder obj)方法入手,在上面的代码中可以看到,主要的作用就是根据传入的Binder对象转换成客户端需要的 AddPersonInter接口。如果服务端和客户端处于同一个进程,那么该方法得到的就是服务端 Stub 对象本身,也就是上面 AIDL 例子 MyService 中的 mBinder 对象;否则返回的是系统封装后的 Stub.Proxy ,也就是一个代理类,在这个代理中实现跨进程通信。

asBinder()

该方法就是返回当前的 Binder 对象。

onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)

在onTransact方法中,根据传入的 code 值会去执行服务端相对应的方法。其中静态变量TRANSACTION_addPerson就是其中的 code 值之一(在 AIDL 文件中声明的方法有多少个就有多少个对应的 code )。其中 data 就是服务端方法中所需要的参数,执行完后,最后把方法的返回结果放入 reply 中传递给客户端。若该方法返回 false ,那么客户端请求失败。

Proxy中的addPerson(com.seventeenok.test.Person person)

Proxy 类是实现了 AddPersonInter接口,把其中的addPerson方法进行了重写。在方法中一开始创建了两个 Parcel 对象,其中一个用来把方法的参数装入,然后调用transact方法执行服务端的代码,执行完后把返回的结果装入另外一个 Parcel 对象中返回。

看完上面方法的介绍,我们回过头来看看 AIDL 例子中实现的流程。在客户端中通过 Intent 去绑定一个服务端的 Service 。在onServiceConnected(ComponentName name, IBinder service)方法中通过返回的 service 可以得到对应的 AIDL 接口的实例。这是调用了asInterface(android.os.IBinder obj)方法来完成的。

客户端在onServiceConnected(ComponentName name, IBinder service)中得到的 service 正是服务端中的 mBinder 。当客户端调用 AIDL 接口时,AIDL 通过 Proxy 类中的addPerson来调用transact方法,transact方法又会去调用服务端的onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags)方法。onTransact方法是运行在服务端的 Binder 线程池中的。在onTransact中根据 code 执行相关 AIDL 接口的方法,方法的参数从 data 中获取。执行完毕之后把结果装入 reply 中返回给客户端。 AIDL 的流程基本上就是这样子了。

总结

AIDL 在 Android IPC 机制中算得上是很重要的一部分,AIDL 主要是通过 Binder 来实现进程通信的。当然,上面只是简单的例子分析AIDL的整个流程,并没有涉及到死亡代理、权限验证等功能,有想法的同学可以深入研究下。

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

推荐阅读更多精彩内容