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、Map)。
    另一端实际接收的具体类始终是 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.aidlpackage 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 CREATOR = new Parcelable.Creator() {        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 服务的几点说明:
  • 对象是跨进程计数的引用。
  • 可以将匿名对象作为方法参数发送。

更多相关文章

  1. google Android编译本地C++程序方法
  2. android 解锁,锁屏流程
  3. 【Android】原生安装和卸载应用
  4. android 高仿多米音乐播放器 (有图有码有真相)
  5. ButterKnife 使用教程
  6. Android(安卓)View框架总结(九)KeyEvent事件分发机制
  7. 面试题及答案
  8. Android事件分发机制的探索与发现之ViewGroup篇
  9. Android(安卓)BaseRecyclerViewAdapterHelper 使用中的一些坑

随机推荐

  1. Android 之Service的使用及生命周期
  2. Android(安卓)Migrate Android(安卓)Code
  3. NDK 环境搭建 - 安装配置 Cygwin
  4. android 圆形碰撞
  5. android webview 任何密度,屏幕大小。
  6. Android面经总结
  7. Android wakelock机制
  8. 将新的驱动源文件添加进android内核进行
  9. android获取手机内部存储空间和外部存储
  10. Android学习笔记(4)——Activity生命周期