Android进程通信-AIDL

Google Doc:https://developer.android.google.cn/guide/components/aidl

AIDL:Android Interface Definition Language,即Android接口定义语言。可以利用它定义客户端与服务使用进程间通信 (IPC) 进行相互通信时都认可的编程接口。

:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder创建接口;或者,如果想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。无论如何,在实现 AIDL 之前,需要理解绑定服务。

从某种意义上说AIDL其实是一个模板,因为在使用过程中,实际起作用的并不是AIDL文件,而是据此而生成的一个IInterface的实例代码,AIDL其实是为了避免我们重复编写代码而出现的一个模板。

通过编写.aidl文件来设计想要暴露的接口,编译后会自动生成相应的.java文件,服务器将接口的具体实现写在Stub中,用IBinder对象传递给客户端,客户端bindService的时候,用asInterface的形式将IBinder还原成接口,再调用其中的方法。

注意: AIDL 接口的调用是直接函数调用。 调用来自本地进程还是远程进程中的线程,实际情况会有所差异。 具体而言:

  • 来自本地进程的调用在发起调用的同一线程内执行。如果该线程是主 UI 线程,则该线程继续在 AIDL 接口中执行。 如果该线程是其他线程,则其便是在服务中执行对应代码的线程。 因此,只有在本地线程访问服务时,我们才能完全控制哪些线程在服务中执行(但如果真是这种情况,根本不应该使用 AIDL,而是应该通过实现 Binder 类创建接口)。
  • 来自远程进程的调用分派自平台在自有进程内部维护的线程池。 必须为来自未知线程的多次并发传入调用做好准备。 换言之,AIDL 接口的实现必须是完全线程安全实现。
  • oneway 关键字用于修改远程调用的行为。使用该关键字时,远程调用不会阻塞;它只是发送事务数据并立即返回。接口的实现最终接收此调用时,是以正常远程调用形式将其作为来自 Binder 线程池的常规调用进行接收。 如果 oneway 用于本地调用,则不会有任何影响,调用仍是同步调用。

AIDL支持的数据类型

  • 基本数据类型(int、long、char、boolean、double、byte、short、float)
  • String 和 CharSequence
  • List、Map集合:
    List/Map中的所有元素都必须是以上列表中支持的数据类型、其他 AIDL 生成的接口或声明的可打包类型。可选择将 List/Map 用作“通用”类(例如,List<String>、Map<String,Integer>)。
    另一端实际接收的具体类始终是 ArrayList/HashMap,但生成的方法使用的是 List/Map 接口。
  • 实现了 Parcelable 接口的对象
  • AIDL本身接口也可以在AIDL文件使用

AIDL使用步骤

  1. 创建 .aidl 文件
    此文件定义带有方法签名的编程接口。
  2. 实现接口
    Android SDK 工具基于您的 .aidl 文件,使用 Java 编程语言生成一个接口。此接口具有一个名为 Stub 的内部抽象类,用于扩展 Binder 类并实现 AIDL 接口中的方法。您必须扩展 Stub 类并实现方法。
  3. 向客户端公开该接口
    实现 Service 并重写 onBind() 以返回 Stub 类的实现。

1)创建.aidl文件

定义服务接口时,需注意:
  • 方法可带零个或多个参数,返回值或空值。
  • 所有非原语参数都需要指示数据走向的方向标记。可以是 inoutinout
    原语默认为 in,不能是其他方向。

定向Tag表示在跨进程通信中数据的流向,用于标注方法的参数值,分为 in、out、inout 三种。

  • in 表示数据只能由客户端流向服务端;
  • out 表示数据只能由服务端流向客户端;
  • inout 则表示数据可在服务端与客户端之间双向流通。

此外,如果AIDL方法接口的参数值类型是:基本数据类型、String、CharSequence或者其他AIDL文件定义的方法接口,那么这些参数值的定向 Tag 默认是且只能是 in,所以除了这些类型外,其他参数值都需要明确标注使用哪种定向Tag。

注意:应该将方向限定为真正需要的方向,因为编组参数的开销极大。

  • .aidl 文件中包括的所有代码注释都包含在生成的 IBinder 接口中(import 和 package 语句之前的注释除外)。
  • 只支持方法;不能公开 AIDL 中的静态字段。

只需将 .aidl 文件保存在项目的 src/ 目录内,SDK 工具会在项目的 gen/ 目录中生成 IBinder 接口文件。生成的文件名与 .aidl 文件名一致,只是使用了 .java 扩展名(例如,IRemoteService.aidl 生成的文件名是 IRemoteService.java)。

.aidl文件示例

// IRemoteService.aidl
package com.example.android;

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

/** Example service interface */
interface IRemoteService {
    /** Request the process ID of this service, to do evil things with it. */
    int getPid();

    /** 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);
}

2)实现接口

Android SDK 工具根据 .aidl 文件生成的 .java 接口包括一个名为 Stub 的子类,这个子类是其父接口(例如,YourInterface.Stub)的抽象实现,用于声明 .aidl 文件中的所有方法

注:Stub 还定义了几个帮助方法,其中 asInterface() 方法带 IBinder(通常便是传递给客户端 onServiceConnected() 回调方法的参数)并返回存根接口实例。

在实现 AIDL 接口时应注意以下几个规则:
  • 由于不能保证在主线程上执行传入调用,因此一开始就需要做好多线程处理准备,并将服务正确地编译为线程安全服务。
  • 默认情况下,RPC 调用是同步调用。如果明知服务完成请求的时间不止几毫秒,就不应该从 Activity 的主线程调用服务,因为这样做可能会使应用挂起,从而导致ANR。通常应该从客户端内的单独线程调用服务。
  • 引发的任何异常都不会回传给调用方。

3)向客户端公开该接口

为服务实现接口后,就需要向客户端公开该接口,以便客户端进行绑定。 要为服务公开该接口,需要扩展 Service 并实现 onBind(),以返回一个类实例,这个类实现了生成的 Stub。

public class RemoteService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Return the interface
        return mBinder;
    }

    private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
        public int getPid(){
            return Process.myPid();
        }
        public void basicTypes(int anInt, long aLong, boolean aBoolean,
            float aFloat, double aDouble, String aString) {
            // Does nothing
        }
    };
}

当客户端(如 Activity)调用 bindService() 以连接此服务时,客户端的 onServiceConnected() 回调会接收服务的 onBind() 方法返回的 mBinder 实例。

客户端还必须具有对 interface 类的访问权限,因此如果客户端和服务在不同的应用内,则客户端的应用 src/ 目录内必须包含 .aidl 文件(它生成 android.os.Binder 接口 — 为客户端提供对 AIDL 方法的访问权限)的副本。

当客户端在 onServiceConnected() 回调中收到 IBinder 时,它必须调用 YourServiceInterface.Stub.asInterface(service) 以将返回的参数转换成 YourServiceInterface 类型。

IRemoteService mIRemoteService;
private ServiceConnection mConnection = new ServiceConnection() {
    // Called when the connection with the service is established
    public void onServiceConnected(ComponentName className, IBinder service) {
        // Following the example above for an AIDL interface,
        // this gets an instance of the IRemoteInterface, which we can use to call on the service
        mIRemoteService = IRemoteService.Stub.asInterface(service);
    }

    // Called when the connection with the service disconnects unexpectedly
    public void onServiceDisconnected(ComponentName className) {
        Log.e(TAG, "Service has unexpectedly disconnected");
        mIRemoteService = null;
    }
};

回调的支持

1)通过RemoteCallbackListregister(IInterface callback)register(IInterface callback,Object cookie)方法注册回调,在多个回调时可以通过cookie判断是哪个回调;通过unregister(IInterface callback)注销回调。
2)AIDL通过广播的方式去调用回调方法。其中RemoteCallbackListgetBroadcastItem(int index)方法可以获取对应的回调类,然后再通过invokeMethod方法执行回调的对应方法。
示例:

   /**
     * 选择要执行的回调的方法
     * @param operateType 回调的cookie(用来确认调用哪一个回调)
     * @param methodName 方法名称
     * @param classes 执行方法的参数类型
     * @param args 执行方法的参数
     */
    public void selectCallbackMethod(String operateType, String methodName,Class[] classes, Object... args) {
        final int N = remoteCallbackList.beginBroadcast();
        for (int i = 0; i < N; i++){
            if (remoteCallbackList.getBroadcastCookie(i).equals(operateType)){
                invokeMethod(remoteCallbackList.getBroadcastItem(i), methodName, classes, args);
            }
        }
        //记得关闭广播
        remoteCallbackList.finishBroadcast();
    }

3)在服务onDestory的时候调用kill方法(kill方法说明:Disable this callback list. All registered callbacks are unregistered and the list is disabled so that future calls to {@link #register} will fail. This should be used when a Service is stopping, to prevent clients from registering callbacks after it is stopped.)
示例:

 @Override
    public void onDestroy() {
        if (remoteCallbackList != null){
            remoteCallbackList.kill();
        }
        super.onDestroy();
    }

通过 IPC 传递对象

通过 IPC 接口可以把某个类从一个进程发送到另一个进程。 但是必须确保该类的代码对 IPC 通道的另一端可用,并且该类必须支持 Parcelable 接口(因为 Android 系统正是通过 Parcelable 接口将对象分解成可编组到各进程的原语)。

创建支持 Parcelable 协议的类,须执行以下操作:
  • 让类实现 Parcelable 接口。
  • 实现 writeToParcel,它会获取对象的当前状态并将其写入 Parcel。
  • 为该类添加一个名为 CREATOR 的静态字段,这个字段是一个实现 Parcelable.Creator 接口的对象。
  • 最后,创建一个声明可打包类的 .aidl 文件。

AIDL 在它生成的代码中使用这些方法和字段将对象编组和取消编组。

示例
以下这个 Rect.aidl 文件可创建一个可打包的 Rect 类:

package android.graphics;

// Declare Rect so AIDL can find it and knows that it implements
// the parcelable protocol.
parcelable Rect;

以下示例展示了 Rect 类如何实现 Parcelable 协议:

import android.os.Parcel;
import android.os.Parcelable;

public final class Rect implements Parcelable {
    public int left;
    public int top;
    public int right;
    public int bottom;

    public static final Parcelable.Creator<Rect> CREATOR = new Parcelable.Creator<Rect>() {
        public Rect createFromParcel(Parcel in) {
            return new Rect(in);
        }

        public Rect[] newArray(int size) {
            return new Rect[size];
        }
    };

    public Rect() {
    }

    private Rect(Parcel in) {
        readFromParcel(in);
    }

    public void writeToParcel(Parcel out) {
        out.writeInt(left);
        out.writeInt(top);
        out.writeInt(right);
        out.writeInt(bottom);
    }

    public void readFromParcel(Parcel in) {
        left = in.readInt();
        top = in.readInt();
        right = in.readInt();
        bottom = in.readInt();
    }
}

调用IPC方法

调用类必须执行以下步骤,才能调用使用 AIDL 定义的远程接口:

  1. 在项目 src/ 目录中加入 .aidl 文件。
  2. 声明一个 IBinder 接口实例(基于 AIDL 生成)。
  3. 实现 ServiceConnection。
  4. 调用 Context.bindService(),以传入 ServiceConnection 实现。
  5. 在 onServiceConnected() 实现中,将收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。
  6. 调用在接口上定义的方法。需始终捕获 DeadObjectException 异常,它们是在连接中断时引发的;这将是远程方法引发的唯一异常。
  7. 如需断开连接,请使用接口实例调用 Context.unbindService()。
有关调用 IPC 服务的几点说明:
  • 对象是跨进程计数的引用。
  • 可以将匿名对象作为方法参数发送。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,117评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,328评论 1 293
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,839评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,007评论 0 206
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,384评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,629评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,880评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,593评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,313评论 1 243
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,575评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,066评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,392评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,052评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,082评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,844评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,662评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,575评论 2 270