Better

业精于勤荒于嬉

AIDL

Better's Avatar 2017-06-25 Android

  1. 1. AIDL
  2. 2. 为什么需要
  3. 3. 怎样实现
    1. 3.1. 定义AIDL接口
    2. 3.2. 实现AIDL接口
    3. 3.3. 向外暴露接口
    4. 3.4. 调用接口进行通讯
  4. 4. 好处
  5. 5. 遇到的错误
    1. 5.1. 提示我AIDL版本不对
    2. 5.2. 找不到符号 XXX.readFromParcel(_reply)

AIDL

AIDL(Android Interface Definition Language,Android接口定义语言)是Android中用于两个进程间通讯(interprocess communication, IPC)且都互相认可的编程接口。

为什么需要

在 Android上由于虚拟机的特性,每个应用都处在自己的进程中 。一个进程通常无法访问另一个进程的内存,这个时候如果需要与其他的进程或者说app进行交互就需要用到AIDL。或者说,为了实现进程之间的相互通信,Andorid采用了一种轻量级的实现方式RPC(Remote Procedure Call),通过AIDL来生成两个进程之间相互访问的代码。
在官网上提到:只有允许不同应用的客户端用 IPC 方式访问服务,并且想要在服务中处理多线程时,才有必要使用 AIDL。 如果您不需要执行跨越不同应用的并发 IPC,就应该通过实现一个 Binder 创建接口;或者,如果您想执行 IPC,但根本不需要处理多线程,则使用 Messenger 类来实现接口。就是说:

  • 应用内部使用:扩展Bindler
  • IPC:Messenger
  • 多线程并IPC:AIDL

怎样实现

必须使用 Java 编程语言语法在 .aidl 文件中定义 AIDL 接口,然后将它保存在托管服务的应用以及任何其他绑定到服务的应用的源代码(src/ 目录)内。通常会以一下四步走:

  1. 定义AIDL文件以及申明接口
  2. 实现AIDL接口
  3. 向客户端暴露接口
  4. 调用接口进行通讯

定义AIDL接口

既然要通讯,这里要明白三个事情。

  1. AIDL默认支持的数据类型:java基本的原始数据类型(int、long、char、boolean)以及String,CharSequence,List,Map。至于其他的数据,只支持序列化Parcelable。序列化的数据在定义接口的时候必须显示的import,而且数据类与申明的数据类的AIDL文件在包名和类名上要意义对应。比如这样:
    IMAGE
  2. 所有非默认支持的参数要指示数据走向的方向标记 in、out 或 inout。
  3. 只支持方法;您不能公开 AIDL 中的静态字段。
    然后创建文件,申明接口。

IMAGE
然后申明接口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import better.bindservices.data.MyMessage;
import better.bindservices.ILocCallBack;
// Declare any non-default types here with import statements

interface IInterCommunication {
// /**
// * Demonstrates some basic types that you can use as parameters
// * and return values in AIDL.
// */
double getLat();

void sendYouAMessage(in String msg);
void sendInt(in int length);

void sendMyMessage(in MyMessage msg);
/**
* 服务端主动与客户端通讯
*/
void registerCallBack(in ILocCallBack callBack);
void unRegisterCallBack(in ILocCallBack callBack);
}

编译的时候会生产.java 扩展名的文件
IMAGE

实现AIDL接口

创建一个服务,申明的时候要显示的标记服务的进程(:表示创建一个只属于当前app新的进程,没有冒号会创建一个都可用的进程)。
IMAGE
我们需要实现实现 .aidl 生成的接口即IInterCommunication.Stub接口,该接口会返回一个Binder对象用于绑定服务。
关键代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private class DemoAidlServicesStub extends IInterCommunication.Stub {

public android.os.RemoteCallbackList<ILocCallBack> mCallbackList = new android.os.RemoteCallbackList();

@Override
public double getLat() throws RemoteException {
return 89.00002;
}

@Override
public void sendYouAMessage(String msg) throws RemoteException {
toastInUi("客户端发来消息:" + msg);
log(msg);
}
...
}

向外暴露接口

实现 Service 并重写 onBind() 以返回 Stub 类的实现。

1
2
3
4
5
6
7
8
9
public class DemoAidlServices extends BaseServices {
DemoAidlServicesStub mBinder = new DemoAidlServicesStub();

@Override
public IBinder onBind(Intent intent) {
// TODO: Return the communication channel to the service.
return mBinder;
}
}

调用接口进行通讯

bind服务,在onServiceConnected()方法中拿到接口对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
bindService(new Intent(MainActivity.this, DemoAidlServices.class), mAidlConnect, Service.BIND_AUTO_CREATE);
private ServiceConnectPlus mAidlConnect = new ServiceConnectPlus() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
super.onServiceConnected(name, service);
mIInterCommunication = IInterCommunication.Stub.asInterface(service);
try {
mIInterCommunication.registerCallBack(mCallBack);
mIInterCommunication.sendInt(8);
} catch (RemoteException e) {
e.printStackTrace();
}
}

@Override
public void onServiceDisconnected(ComponentName name) {
super.onServiceDisconnected(name);
try {
mIInterCommunication.unRegisterCallBack(mCallBack);
} catch (RemoteException e) {
e.printStackTrace();
}
mIInterCommunication = null;
}
};

onServiceConnected() 实现中,收到一个 IBinder 实例(名为 service)。调用 YourInterfaceName.Stub.asInterface((IBinder)service),以将返回的参数转换为 YourInterface 类型。拿到接口后就可以在客户端端于服务端主动通讯了(get和send)。
get

1
2
3
4
5
6
7
8
9
if (mAidlConnect.isBind()) {
try {
toast("获取到:" + mIInterCommunication.getLat());
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
toast("还没准备好");
}

send

1
2
3
4
5
6
7
8
9
10
if (mAidlConnect.isBind()) {
try {
mIInterCommunication.sendYouAMessage("Fuck");
mIInterCommunication.sendMyMessage(new MyMessage("张三", "哈哈哈"));
} catch (RemoteException e) {
e.printStackTrace();
}
} else {
toast("还没准备好");
}

如果服务想主动向客户端通讯,必须通过注册回调的方式来进行。比如在onServiceConnected()中的registerCallBack
注册回调的时候要注意,如果客户端被意外的杀死,我们需要取消注册的回调。不然当服务调用回调的时候会发生DeadObjectException,还会浪费资源(引用),但是这种情况服务是不方便收到app进程被杀死的消息的。
Binder提供了进程得到意外退出通知的机制:Link-To-Death,Android提供了RemoteCallbackList来处理这个典型的使用场景。

好处

把服务开启在一个新的进程的可见减少对app进程的内存占用,减少应用的app进程没回收的概率。在IM通讯中会把通讯的逻辑放在新的进程中,比如逸创云客服的聊天。

遇到的错误

提示我AIDL版本不对

1
2
Error:Execution failed for task ':app:compileDebugAidl'.
> java.lang.RuntimeException: com.android.ide.common.process.ProcessException: Error while executing process /Users/better/Documents/program/Android/SDK/build-tools/25.0.2/aidl with arguments {-p/Users/better/Documents/program/Android/SDK/platforms/android-25/framework.aidl

问题出在我想传递一个序列化对象,结果.java文件与.aidl文件路径不一致。
就是说传递的序列化对象路径和文件名要一致。

找不到符号 XXX.readFromParcel(_reply)

这里将序列化对象的流向改为out,报此错。
查看aidl生产的.java文件,对应的方法显示的调用了readFromParcel()。所以在实体类已经实现Parcelable的情况下,会有一个带Parcel参数的构造方法,没错,复制该构造方法改为为pubic void,改掉方法名为readFromParcel即可。

This article was last updated on days ago, and the information described in the article may have changed.