Android夸进程通信机制八:使用 AIDL进行进程间通信


Android夸进程通信机制系列:
Android夸进程通信机制一:多进程简介
Android夸进程通信机制二:Parcel 与 Parcelable
Android夸进程通信机制三:Messenger与Message
Android夸进程通信机制四:使用 Bundle进行进程间通信
Android夸进程通信机制五:使用文件共享进行进程间通信
Android夸进程通信机制六:使用ContentProvider进行进程间通信
Android夸进程通信机制七:使用 Socket进行进程间通信
Android夸进程通信机制八:使用 AIDL进行进程间通信
Android夸进程通信机制九:AIDL深入了解
...


一、前言

IPC(interprocess communication)是指进程间通信,也就是在两个进程间进行数据交互。不同的操作系统都有他们自己的一套IPC机制。例如在Linux操作系统中可以通过管道、信号量、消息队列、内存共享、套接字等进行进程间通信。

那么在Android系统中我们可以通过Binder来进行进程间的通信,当然除了Binder我们还可以使用Socket和文件共享来进行进程间的通信。

前面几节已经学习了文件共享的方式、Socket方式和Binder中的其他几种方式,下面就来介绍BInder的另一种方式-AIDL。

二、AIDL介绍

1、AIDL定义

AIDL( Android Interface Definition Language),译为:android接口定义语言,是一种IDL语言,用于生成可以在Android设备上两个进程之间进行 IPC的代码。

如果在一个进程中(例如Activity)要调用另一个进程中(例如Service)对象的操作,就可以使用AIDL生成可序列化的参数。

2、AIDL语法规则

既然AIDL是一种语言,那么,他就有直接的语法规则,下面简单介绍一下几点规则。

默认情况下AIDL支持以下数据类型:

  • 所有Java的基本数据类型(例如: int, long,double, char, boolean等)
  • String和CharSequence
  • List:AIDL实际接收到的是ArrayList,并且List里面所有元素都必须被AIDL支持
  • Map: AIDL实际接收到的是HashMap,并且Map里面所有元素都必须被AIDL支持
  • 序列化数据,即系实现Parcelable和Serializable接口的数据

注意,对于序列化的数据,我们必须要显示import进来,即使他们在同一个包中。当我们使用自定义的对象时必须实现Parcelable接口,Parcelable为对象序列化接口,效率比实现Serializable接口高。并且新建一个与该类同名的AIDL文件,声明他为Parcelable类型。

我们定义AIDL接口还需要注意以下几点:

  • 方法可以有多个或没有参数,可以有返回值也可以为void
  • 在参数中,除了基本类型以外,我们必须为参数标上方向in, out, 或者 inout
  • 在AIDL文件中只支持方法,不支持静态常量

3、AIDL机制

AIDL IPC机制是面向接口的,像COM或Corba一样,但是更加轻量级。它是使用代理类在客户端和实现端传递数据。

AIDL规定了两个进程之间如何使用Binder来通信,AIDL文件就是接口和数据声明。Android利用我们写好的AIDL文件生成Java文件,真正在打包成apk文件时会把aidl文件生成的java代码打包进去,不会打包AIDL文件,那么很好理解,Android利用我们写好的AIDL文件生成基于Binder进程通信java代码。Binder进程通信是C/S和代理模式实现的,我们在下节课学习一下Binder。

基于Binder的AIDL数据通信流程图

三、AIDL使用流程

通过上面的DIDL数据通信流程图,我们知道,AIDL的核心是BInder驱动,所以,我们就很自然的把AIDL的用法分 为是三部分

binder实现、客户端调用、服务端调用

虽然这样划分没有上面毛病,但是,AIDL的实现过程还是稍显麻烦的,初用AIDL的童鞋可能会不知所措,因此,我整理了一个实现Aidl的基本流程,把最关键的Binder实现部分细分前后关联的6步,让人一看就清楚明了,流程如下:

1、定义数据实体类;
2、添加数据实体类的映射 aidl 文件;
3、添加AIDL接口描述文件;
4、编译生成Binder 的 Java 文件;
5、服务端 IUseAidlInterface.Stub;
6、客户端 Stub.asInterface(IBinder);

当然,如果我们只用到AIDL直接支持的数据,可以省掉前两步,但,这个与我们实际的开发并不吻合,因为,我们的开发都是需要自定义数据实体类来描述对象的属性的,这就告诉我们前两步在实际开发中也是必须的。

按照这个流程,我们就能非常顺利的使用AIDL进行进程间的通信了。

四、 AIDL用法详解

下面,我们就通过一个栗子,详细讲解一下流程以及需要注意的地方。

栗子的目的是,通过AIDL实现如下功能:
客户端调用服务端的远程接口向服务端添加音乐数据,并在需要时获取全部的音乐数据。

1、定义数据实体类

我们做开发的时候,正常来说都是需要自定义一个描述属性的数据类,里面包含各种对对象属性描述的数据,如,我们之前讲bundler的时候,就定义了一个MusicData的数据模型来描述一个歌曲的属性信息。

而前面已经说过,AIDL使用非支持数据(非基础数据类型和支持的JAVA数据类型)时,都需要数据类现实Parcelable或者Serializable接口,鉴于性能而且是Android的开发,所以,我们的MusicData也是需要实现Parcelable接口。

/**
 * Copyright (C), 2015-2019, 雨纷纷工作室
 * FileName: MusicData
 * Author: yufenfen
 * Date: 2016/4/8 5:21 PM
 * Description: IPC之AIDL方式,数据模型
 */
package yb.demo.myProcesses.useAIDL.dataModel;

import ……;

/**
 * @ClassName: MusicData
 * @Description: IPC之AIDL方式,数据模型
 * @Author: yufenfen
 * @Date: 2016/4/8 5:21 PM
 */

public class MusicData implements Parcelable {
    ……
    ……
    ……
}

2、添加数据实体类的映射 aidl 文件

添加好数据模型后,就可以添加对应的映射 aidl 文件了,

Android 目录结构直接提供了一个aidl文件夹,当然,当我们新添加aidl文件的时候才会显现,这个文件夹里可以根据不同的包实现不同的AIDL文件。

目前,无论是Eclipse还是Android Studio对aidl文件的编写都还是很不智能,就像是写txt文本文件,没有智能提示的。而且关键的是里面的package 包名和import导包都要十分小心,不能写错了,否则,会直接报错:无法找到类或者无法识别类。

另外,数据实体类及其映Aidl文件的类名、包名以及文件名要保持一致,否则,也会直接报错:无法找到类或者无法识别类。

所以,为了避免报错,必须要让他们保持一致。最好的方法是

  • 让AIDL文件自动生成,就是去到我们要添加的AIDL的报名文件上,直接右键选择添加AIDL文件:
  • import包时,直接拷贝包名进来

我们这里添加的映射 aidl 文件的内容如下:

// MusicData.aidl
package yb.demo.myProcesses.useAIDL.dataModel;

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

//要和声明的实体类在一个包里
parcelable MusicData;

3、添加AIDL接口描述文件

经过前面两步,已经添加了AIDL接口描述文件所需要的数据类及其映射 aidl 文件,接下里就可以添加AIDL接口描述文件,定义接口使用数据了。

按照之前说的,为了避免报错,我们还是在包文件夹上,右键选择添加DID文件,自动添加后,就可以根据实际需要,修改AIDL文件了。

另外,再重复一下,要注意以下几点:

  • Android Studio aidl文件夹里的包名要跟对应java实体类包名一致
  • aidl文件里面package 包名和import导包都要跟java实体类包名一致
  • 接口和方法前不用加访问权限修饰符public,private,protected等,也不能用final,static。
  • 在接口参数中,除了基本类型以外,我们必须为参数标上方向in, out, 或者 inout

这里添加的接口文件,大致如下:

// IMyAidlInterface.aidl
package yb.demo.myProcesses.useAIDL;

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

import yb.demo.myProcesses.MusicData;

interface IUseAidlInterface {
    /**
     * 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);
    //根据需求,新添加,MusicData要实现Parcelable接口
    void addMusic(in MusicData data);

    List<MusicData> getMusicList();
}

4、编译生成Binder 的 Java 文件

OK,AIDL接口文件添加完毕后,我们重新编译一下项目,顺利编译通过的话,就会为我们生成复杂的Binder Java 文件。
binder 的 java 文件生成在以下目录:

build/generated/aidl_source_output_dir/debug/compileDebugAidl/out/

如果,没有顺利通过,就回去查查看,哪里有问题,问题一般出在类名、文件名和包名不一致。

通过这几步,AIDL相关的基础已经准备好,并且编译生成了对应的binder Java文件,接下来,我们就可以在服务端和客户端使用AIDL进行进程间的通信了。

5、服务端

经过前面几步,准备好了使用Binder,接下来,创建你喜欢的Service,并在其中创建上面生成的 Binder 对象实例(IUseAidlInterface.Stub),实现接口定义的方法;

当客户端与服务端绑定时,服务端是通过onBind()将 mIBinder 返回给客户端的,客户端拿到mIBinder就可以通过它远程调用服务端的方法,实现通讯了。

   /**
 * Copyright (C), 2015-2019, 雨纷纷工作室
 * FileName: IPCAIDLClientActivity
 * Author: yufenfen
 * Date: 2016/4/8 5:41 PM
 * Description: IPC之AIDL方式,服务端
 */
package yb.demo.myProcesses.useAIDL;

import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;

import java.util.ArrayList;
import java.util.List;

import yb.demo.myProcesses.useAIDL.dataModel.MusicData;


/**
 * @ClassName: IPCAIDLClientActivity
 * @Description: IPC之AIDL方式,服务端
 * @Author: yufenfen
 * @Date: 2016/4/8 5:41 PM
 */

public class IPCAIDLService extends Service {

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

    private List<MusicData> mData = new ArrayList<MusicData>();

    IBinder mIBinder = new IUseAidlInterface.Stub() {
        @Override
        public void addMusic(MusicData data) throws RemoteException {
            mData.add(data);
        }

        @Override
        public List<MusicData> getMusicList() throws RemoteException {
            return mData;
        }
    };

}

6、 客户端

一切准备就绪了,就差客户端调用这一步了,我们新建一个activity作为客户端,实现流程如下:

6.1、 实现ServiceConnection 接口

实现ServiceConnection 接口并在onServiceConnected()中拿到 IBinder,并通过AIDL的Stub.asInterface(IBinder) 方法将 IBinder 转为 AIDL 类。

public class IPCAIDLClientActivity extends Activity {
    private IUseAidlInterface mIUseAidlInterface;
    ……
    private ServiceConnection mServiceConnection = new ServiceConnection(){
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            //连接后拿到 Binder,转换成 AIDL,在不同进程会返回个代理
            mIUseAidlInterface = IUseAidlInterface.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            mIUseAidlInterface = null;
        }
    };
    ……
}
6.2、绑定与解绑服务

前面几节已经讲过,要利用Binder进行IPC,就必须要通过Android的 bindService() 方法将应用绑定到服务上。

public class IPCAIDLClientActivity extends Activity {
    ……
    @Override
    protected void onStart() {
        super.onStart();

        Intent aidlIntent = new Intent(getApplicationContext(), IPCAIDLService.class);
        bindService(aidlIntent, mServiceConnection, BIND_AUTO_CREATE);
    }

    @Override
    protected void onStop() {
        super.onStop();
        
        unbindService(mServiceConnection);
    }
    ……
}
6.3、跨进程调用

拿到 AIDL 类,并且绑定服务后,就可以调用 AIDL 类中定义好的操作,进行跨进程通信了。

public class IPCAIDLClientActivity extends Activity {
    ……
    public void addMusic(View v) {
        String coverUri = "https://gss1.bdstatic.com/-vo3dSag_xI4khGkpoWK1HF6hhy/baike/c0%3Dbaike150%2C5%2C5%2C150%2C50/sign=1a322bc2c9fcc3cea0cdc161f32cbded/d01373f082025aaff92dd92bfaedab64034f1a36.jpg";
        MusicData musicD = new MusicData.Builder().album("七里香").cover(coverUri).name("七里香").singer("周杰伦").build();

        try {
            mIUseAidlInterface.addMusic(musicD);
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

    public void getAllMusic(View v) {
        try {
            List<MusicData> musicList = mIUseAidlInterface.getMusicList();
            
        } catch (RemoteException e) {
            e.printStackTrace();
        }
    }

}

OK,到这里,经过以上六步,相信你的demo已经顺利的跑起来了。

五、结语

这一节还是比较费文字的,详细地介绍了 实现使用AIDL在进程间进行通信 的基本流程,结合实例讲解和提出注意点,比如文件名、类名以及文件所在包的路径不统一……,旨在把AIDL的使用描述清楚,相信看到这里,你已经把握了它的用法。

但是我们还是有很多疑问的,比如:
流程中,第四步,编译生成Binder 的 Java 文件,它的内容是怎么样的,他是如何生成的?
还有,这一节讲的AIDL以及之前讲的 Messenger、Bundle 和 Content Provider 都是基于Binder实现的,那么BInder又是什么呢?
……
其实,我们都还是停留在怎么用,而没有深入了解为什么这样用,也就是知其然还要知其所以然。

下一节我们深入了解 Binder,要知其所以然。

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

推荐阅读更多精彩内容