Binder机制05-AIDL
AIDL简介¶
什么是AIDL¶
AIDL : Android Interface Definition Language,即Android接口定义语言。
Android系统中的进程之间不能共享内存,因此,需要提供一些机制在不同进程之间进行数据通信。为了使其他的应用程序也可以访问本应用程序提供的服务,Android系统采用了远程过程调用(Remote Procedure Call,RPC)方式来实现。与很多其他的基于RPC的解决方案一样,Android使用一种接口定义语言(Interface Definition Language,IDL)来公开服务的接口。我们知道4个Android应用程序组件中的3个(Activity、BroadcastReceiver和ContentProvider)都可以进行跨进程访问,另外一个Android应用程序组件Service同样可以。因此,可以将这种可以跨进程访问的服务称为AIDL(Android Interface Definition Language)服务。
AIDL基本语法¶
AIDL使用简单的语法来声明接口,描述其方法以及方法的参数和返回值。这些参数和返回值可以是任何类型,甚至是其他AIDL生成的接口。
其中对于Java编程语言的基本数据类型 (int, long, char, boolean,String,CharSequence)集合接口类型List和Map,不需要import 语句。而如果需要在AIDL中使用其他AIDL接口类型,需要import,即使是在相同包结构下。AIDL允许传递实现Parcelable接口的类,需要import。
需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout。 in:client端将参数设定好,传给server,server端拷贝、使用。 out:client将接口、地址等“空壳”传给server,server将运算结果填充进去,然后client从中取出运算结果。 inout:client端既从此处传入数据,又从此处取出server填充的结果。
将完成in功能的设成out、或out功能的设成in,都会报错。若将连个均设成inout,则通过,但增加了序列化的步骤,白白浪费运算资源。
aidl文件¶
// IPerformanceService.aidl
package com.rtfsc.aidldemo;
interface IPerformanceService {
oneway void setProcessPriority(int pid, int priority);
void setThreadPriority(int tid, int priority);
int getThreadPriority(int pid);
void copyArrayIn(in String[] source);
void copyArrayOut(out String[] source);
void copyArrayInOut(inout String[] source);
}
然后用编译工具编译之后,可以得到对应的 IPerformanceService.java 类,看看系统给我们生成的代码:
Stub类¶
/*
* This file is auto-generated. DO NOT MODIFY.
*/
package com.rtfsc.i007service;
public interface IPerformanceService extends android.os.IInterface {
/**
* Default implementation for IPerformanceService.
*/
public static class Default implements com.rtfsc.i007service.IPerformanceService {
@Override
public void setProcessPriority(int pid, int priority) throws android.os.RemoteException {
}
@Override
public void setThreadPriority(int tid, int priority) throws android.os.RemoteException {
}
@Override
public int getThreadPriority(int pid) throws android.os.RemoteException {
return 0;
}
@Override
public void copyArrayIn(java.lang.String[] source) throws android.os.RemoteException {
}
@Override
public void copyArrayOut(java.lang.String[] source) throws android.os.RemoteException {
}
@Override
public void copyArrayInOut(java.lang.String[] source) throws android.os.RemoteException {
}
@Override
public android.os.IBinder asBinder() {
return null;
}
}
/**
* Local-side IPC implementation stub class.
*/
public static abstract class Stub extends android.os.Binder implements com.rtfsc.i007service.IPerformanceService {
private static final java.lang.String DESCRIPTOR = "com.rtfsc.i007service.IPerformanceService";
/**
* Construct the stub at attach it to the interface.
*/
public Stub() {
this.attachInterface(this, DESCRIPTOR);
}
/**
* Cast an IBinder object into an com.rtfsc.i007service.IPerformanceService interface,
* generating a proxy if needed.
*/
public static com.rtfsc.i007service.IPerformanceService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.rtfsc.i007service.IPerformanceService))) {
return ((com.rtfsc.i007service.IPerformanceService) iin);
}
return new com.rtfsc.i007service.IPerformanceService.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 {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
case TRANSACTION_setProcessPriority: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
this.setProcessPriority(_arg0, _arg1);
return true;
}
case TRANSACTION_setThreadPriority: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
this.setThreadPriority(_arg0, _arg1);
reply.writeNoException();
return true;
}
case TRANSACTION_getThreadPriority: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _result = this.getThreadPriority(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_copyArrayIn: {
data.enforceInterface(descriptor);
java.lang.String[] _arg0;
_arg0 = data.createStringArray();
this.copyArrayIn(_arg0);
reply.writeNoException();
return true;
}
case TRANSACTION_copyArrayOut: {
data.enforceInterface(descriptor);
java.lang.String[] _arg0;
int _arg0_length = data.readInt();
if ((_arg0_length < 0)) {
_arg0 = null;
} else {
_arg0 = new java.lang.String[_arg0_length];
}
this.copyArrayOut(_arg0);
reply.writeNoException();
reply.writeStringArray(_arg0);
return true;
}
case TRANSACTION_copyArrayInOut: {
data.enforceInterface(descriptor);
java.lang.String[] _arg0;
_arg0 = data.createStringArray();
this.copyArrayInOut(_arg0);
reply.writeNoException();
reply.writeStringArray(_arg0);
return true;
}
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
private static class Proxy implements com.rtfsc.i007service.IPerformanceService {
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 void setProcessPriority(int pid, int priority) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(pid);
_data.writeInt(priority);
boolean _status = mRemote.transact(Stub.TRANSACTION_setProcessPriority, _data, null, android.os.IBinder.FLAG_ONEWAY);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setProcessPriority(pid, priority);
return;
}
} finally {
_data.recycle();
}
}
@Override
public void setThreadPriority(int tid, int priority) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(tid);
_data.writeInt(priority);
boolean _status = mRemote.transact(Stub.TRANSACTION_setThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setThreadPriority(tid, priority);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public int getThreadPriority(int pid) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(pid);
boolean _status = mRemote.transact(Stub.TRANSACTION_getThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getThreadPriority(pid);
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
@Override
public void copyArrayIn(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStringArray(source);
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayIn, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayIn(source);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void copyArrayOut(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((source == null)) {
_data.writeInt(-1);
} else {
_data.writeInt(source.length);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayOut, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayOut(source);
return;
}
_reply.readException();
_reply.readStringArray(source);
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void copyArrayInOut(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStringArray(source);
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayInOut, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayInOut(source);
return;
}
_reply.readException();
_reply.readStringArray(source);
} finally {
_reply.recycle();
_data.recycle();
}
}
public static com.rtfsc.i007service.IPerformanceService sDefaultImpl;
}
static final int TRANSACTION_setProcessPriority = (android.os.IBinder.FIRST_CALL_TRANSACTION + 0);
static final int TRANSACTION_setThreadPriority = (android.os.IBinder.FIRST_CALL_TRANSACTION + 1);
static final int TRANSACTION_getThreadPriority = (android.os.IBinder.FIRST_CALL_TRANSACTION + 2);
static final int TRANSACTION_copyArrayIn = (android.os.IBinder.FIRST_CALL_TRANSACTION + 3);
static final int TRANSACTION_copyArrayOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 4);
static final int TRANSACTION_copyArrayInOut = (android.os.IBinder.FIRST_CALL_TRANSACTION + 5);
public static boolean setDefaultImpl(com.rtfsc.i007service.IPerformanceService impl) {
// Only one user of this interface can use this function
// at a time. This is a heuristic to detect if two different
// users in the same process use this function.
if (Stub.Proxy.sDefaultImpl != null) {
throw new IllegalStateException("setDefaultImpl() called twice");
}
if (impl != null) {
Stub.Proxy.sDefaultImpl = impl;
return true;
}
return false;
}
public static com.rtfsc.i007service.IPerformanceService getDefaultImpl() {
return Stub.Proxy.sDefaultImpl;
}
}
public void setProcessPriority(int pid, int priority) throws android.os.RemoteException;
public void setThreadPriority(int tid, int priority) throws android.os.RemoteException;
public int getThreadPriority(int pid) throws android.os.RemoteException;
public void copyArrayIn(java.lang.String[] source) throws android.os.RemoteException;
public void copyArrayOut(java.lang.String[] source) throws android.os.RemoteException;
public void copyArrayInOut(java.lang.String[] source) throws android.os.RemoteException;
}
系统帮我们生成了这个文件之后,我们只需要继承 IPerformanceService.Stub 这个抽象类,实现它的方法,然后在Service 的onBind方法里面返回就实现了AIDL。
这个Stub类非常重要,具体看看它做了什么。
Stub类继承自Binder¶
public static abstract class Stub extends android.os.Binder implements com.rtfsc.i007service.IPerformanceService {
...
}
Stub类继承自Binder,意味着这个Stub其实自己是一个Binder本地对象。
Stub实现 IPerformanceService 接口¶
public static abstract class Stub extends android.os.Binder implements com.rtfsc.i007service.IPerformanceService {
...
}
IInterface¶
IPerformanceService 本身是一个IInterface。
因此他携带某种客户端需要的能力(这里是方法 setProcessPriority等等 )。此类有一个内部类Proxy,也就是Binder代理对象。
asInterface¶
/**
* Cast an IBinder object into an com.rtfsc.i007service.IPerformanceService interface, * generating a proxy if needed. */public static com.rtfsc.i007service.IPerformanceService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.rtfsc.i007service.IPerformanceService))) {
return ((com.rtfsc.i007service.IPerformanceService) iin);
}
return new com.rtfsc.i007service.IPerformanceService.Stub.Proxy(obj);
}
看看 asInterface 方法,我们在bind一个Service之后,在onServiceConnecttion的回调里面,就是通过这个方法拿到一个远程的service的。
函数的入参obj是 IBinder 类型。 如果是Binder本地对象,那么它就是 Binder 类型; 如果是Binder代理对象,那就是 BinderProxy 类型。
正如上面自动生成的文档所说,它会试着查找Binder本地对象,如果找到,说明Client和Server都在同一个进程,这个参数直接就是本地对象,直接强制类型转换然后返回。
如果找不到,说明是远程对象(处于另外一个进程)那么就需要创建一个Binde代理对象,让这个Binder代理实现对于远程对象的访问。一般来说,如果是与一个远程Service对象进行通信,那么这里返回的一定是一个Binder代理对象,这个IBinder参数的类型是BinderProxy。
打开写demo调试的时候可以指定service在另一个进程里,观察asInterface方法用的是 Binder 还是 BinderProxy 可以在Androidmanifests文件中Service配置android:process=":service"
Proxy¶
再看看我们对于aidl的 getThreadPriority 方法的实现;在Stub类里面, getThreadPriority 是一个抽象方法,我们需要继承这个类并实现它。
如果Client和Server在同一个进程,那么直接就是调用这个方法;如果是远程调用,是通过Binder代理完成的,在这个例子里面就是 Proxy 类; Proxy 对于 getThreadPriority 方法的实现如下:
@Override
public int getThreadPriority(int pid) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(pid);
boolean _status = mRemote.transact(Stub.TRANSACTION_getThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getThreadPriority(pid);
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
它首先用 Parcel 把数据序列化了,然后调用了 transact 方法。
transact¶
这个 transact 到底做了什么呢?这个 Proxy 类在 asInterface 方法里面被创建,如果是Binder代理那么说明驱动返回的IBinder实际是 BinderProxy ,因此我们的 Proxy 类里面的 mRemote 实际类型应该是 BinderProxy ;我们看看 BinderProxy 的 transact 方法:(Binder.java的内部类)
frameworks/base/core/java/android/os/IBinder.java
/**
* Perform a generic operation with the object. ** @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel * that is given here. * @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value. * @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
* * @return Returns the result from {@link Binder#onTransact}. A successful call
* generally returns true; false generally means the transaction code was not
* understood. For a oneway call to a different process false should never be
* returned. If a oneway call is made to code in the same process (usually to
* a C++ or Rust implementation), then there are no oneway semantics, and
* false can still be returned.
*/
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
frameworks/base/core/java/android/os/BinderProxy.java
/**
* Java proxy for a native IBinder object. * Allocated and constructed by the native javaObjectforIBinder function. Never allocated * directly from Java code. * * @hide
*/
public final class BinderProxy implements IBinder {
/**
* Perform a binder transaction on a proxy. * * @param code The action to perform. This should
* be a number between {@link #FIRST_CALL_TRANSACTION} and
* {@link #LAST_CALL_TRANSACTION}.
* @param data Marshalled data to send to the target. Must not be null.
* If you are not sending any data, you must create an empty Parcel * that is given here. * @param reply Marshalled data to be received from the target. May be
* null if you are not interested in the return value. * @param flags Additional operation flags. Either 0 for a normal
* RPC, or {@link #FLAG_ONEWAY} for a one-way RPC.
* * @return
* @throws RemoteException
*/
public boolean transact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
Binder.checkParcel(this, code, data, "Unreasonably large binder buffer");
boolean warnOnBlocking = mWarnOnBlocking; // Cache it to reduce volatile access.
if (warnOnBlocking && ((flags & FLAG_ONEWAY) == 0)
&& Binder.sWarnOnBlockingOnCurrentThread.get()) {
// For now, avoid spamming the log by disabling after we've logged
// about this interface at least once mWarnOnBlocking = false;
warnOnBlocking = false;
if (Build.IS_USERDEBUG || Build.IS_ENG) {
// Log this as a WTF on userdebug and eng builds.
Log.wtf(Binder.TAG,
"Outgoing transactions from this process must be FLAG_ONEWAY",
new Throwable());
} else {
Log.w(Binder.TAG,
"Outgoing transactions from this process must be FLAG_ONEWAY",
new Throwable());
}
}
final boolean tracingEnabled = Binder.isStackTrackingEnabled();
if (tracingEnabled) {
final Throwable tr = new Throwable();
Binder.getTransactionTracker().addTrace(tr);
StackTraceElement stackTraceElement = tr.getStackTrace()[1];
Trace.traceBegin(Trace.TRACE_TAG_ALWAYS,
stackTraceElement.getClassName() + "." + stackTraceElement.getMethodName());
}
// Make sure the listener won't change while processing a transaction.
final Binder.ProxyTransactListener transactListener = sTransactListener;
Object session = null;
if (transactListener != null) {
final int origWorkSourceUid = Binder.getCallingWorkSourceUid();
session = transactListener.onTransactStarted(this, code, flags);
// Allow the listener to update the work source uid. We need to update the request
// header if the uid is updated. final int updatedWorkSourceUid = Binder.getCallingWorkSourceUid();
if (origWorkSourceUid != updatedWorkSourceUid) {
data.replaceCallingWorkSourceUid(updatedWorkSourceUid);
}
}
final AppOpsManager.PausedNotedAppOpsCollection prevCollection =
AppOpsManager.pauseNotedAppOpsCollection();
if ((flags & FLAG_ONEWAY) == 0 && AppOpsManager.isListeningForOpNoted()) {
flags |= FLAG_COLLECT_NOTED_APP_OPS;
}
try {
final boolean result = transactNative(code, data, reply, flags);
if (reply != null && !warnOnBlocking) {
reply.addFlags(Parcel.FLAG_IS_REPLY_FROM_BLOCKING_ALLOWED_OBJECT);
}
return result;
} finally {
AppOpsManager.resumeNotedAppOpsCollection(prevCollection);
if (transactListener != null) {
transactListener.onTransactEnded(session);
}
if (tracingEnabled) {
Trace.traceEnd(Trace.TRACE_TAG_ALWAYS);
}
}
}
/**
* Native implementation of transact() for proxies
*/
public native boolean transactNative(int code, Parcel data, Parcel reply,
int flags) throws RemoteException;
}
frameworks/base/core/jni/android_util_Binder.cpp
static const JNINativeMethod gBinderProxyMethods[] = {
/* name, signature, funcPtr */
{"transactNative", "(ILandroid/os/Parcel;Landroid/os/Parcel;I)Z", (void*)android_os_BinderProxy_transact},
};
static jboolean android_os_BinderProxy_transact(JNIEnv* env, jobject obj,
jint code, jobject dataObj, jobject replyObj, jint flags) // throws RemoteException
{
if (dataObj == NULL) {
jniThrowNullPointerException(env, NULL);
return JNI_FALSE;
}
Parcel* data = parcelForJavaObject(env, dataObj);
if (data == NULL) {
return JNI_FALSE;
}
Parcel* reply = parcelForJavaObject(env, replyObj);
if (reply == NULL && replyObj != NULL) {
return JNI_FALSE;
}
IBinder* target = getBPNativeData(env, obj)->mObject.get();
if (target == NULL) {
jniThrowException(env, "java/lang/IllegalStateException", "Binder has been finalized!");
return JNI_FALSE;
}
ALOGV("Java code calling transact on %p in Java object %p with code %" PRId32 "\n",
target, obj, code);
bool time_binder_calls;
int64_t start_millis;
if (kEnableBinderSample) {
// Only log the binder call duration for things on the Java-level main thread.
// But if we don't time_binder_calls = should_time_binder_calls();
if (time_binder_calls) {
start_millis = uptimeMillis();
}
}
//printf("Transact from Java code to %p sending: ", target); data->print();
status_t err = target->transact(code, *data, reply, flags);
//if (reply) printf("Transact from Java code to %p received: ", target); reply->print();
if (kEnableBinderSample) {
if (time_binder_calls) {
conditionally_log_binder_call(start_millis, target, code);
}
}
if (err == NO_ERROR) {
return JNI_TRUE;
} else if (err == UNKNOWN_TRANSACTION) {
return JNI_FALSE;
}
signalExceptionForError(env, obj, err, true /*canThrowRemoteException*/, data->dataSize());
return JNI_FALSE;
}
从这可以看出它的实现在native层,里面进行了一系列的函数调用。
调用链实在太长这里就不给出了;要知道的是它最终调用到了 talkWithDriver 函数;看这个函数的名字就知道,通信过程要交给驱动完成了;最终通过 ioctl 系统调用,Client进程陷入内核态,Client进程挂起等待返回;驱动完成一系列的操作之后唤醒Server进程,调用了Server进程本地对象的 onTransact 函数。我们在看Binder本地对象的 onTransact 方法(这里就是 Stub 类里面的此方法)。
onTransact¶
@Override
public boolean onTransact(int code, android.os.Parcel data, android.os.Parcel reply, int flags) throws android.os.RemoteException {
java.lang.String descriptor = DESCRIPTOR;
switch (code) {
case INTERFACE_TRANSACTION: {
reply.writeString(descriptor);
return true;
}
...
case TRANSACTION_getThreadPriority: {
data.enforceInterface(descriptor);
int _arg0;
_arg0 = data.readInt();
int _result = this.getThreadPriority(_arg0);
reply.writeNoException();
reply.writeInt(_result);
return true;
}
...
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
在Server进程里面, onTransact 根据调用号(每个AIDL函数都有一个编号,在跨进程的时候,不会传递函数,而是传递编号指明调用哪个函数)调用相关函数;在这个例子里面,调用了Binder本地对象的 getThreadPriority 方法;这个方法将结果返回给驱动,驱动唤醒挂起的Client进程并将结果返回。于是一次跨进程调用就完成了。
小结¶
有一个Stub的抽象类,Stub中又有一个Proxy的抽象类。
Stub.asInterface(IBinder) 会根据是同一进行通信,返回Stub()实体;不同进程通信,返回Stub.Proxy()代理对象。
Stub()的Binder实体中有个onTransact()函数,在前面的一些Binder Native、Framework的流程,我们知道,服务最终处理的入口就是onTransact(),这里会解析Client传来的 TRANSACTION code ,解析Parcel数据,调用对应的服务接口处理。
Proxy()中存在一个asBinder(),返回的对象为mRemote,就如我们前面Framework了解的,对应的其实是BinderProxy对象。
Proxy()组装了Client中的AIDL接口的核心实现,组装Parcel数据,调用BinderProxy()的transact()发送TRANSACTION code。
AIDL通信流程¶
Client 端和Server端使用同一个AIDL,连包名都需要保持一致。
Server端继承自Service,重载一个onBind(),返回服务实体Stub(),Stub提供了一个asInterface(Binder)的方法,如果是在同一个进程下那么asInterface()将返回Stub对象本身,否则返回Stub.Proxy对象。
IPerformanceService.Stub mStub = new IPerformanceService.Stub(){...};
@Override
public IBinder onBind(Intent intent) {
return mStub;//通过ServiceConnection在activity中拿到 PerformanceService
}
Client 绑定服务时通过拿到服务Stub.asInterface返回的服务的代理Stub.Proxy()。
Client和Server交互的简单示意流程:
服务本地拿到了AIDL生成的服务实体Stub(), Client绑定服务后,拿到了服务的代理Stub.proxy()。这和我们在前面Framewok层的比较类似了,Client拿到BinderProxy对象,Server拿到Binder实体对象。
AIDL在这里用到了一个Proxy-Stub (代理-存根)的设计模式,下面我们就这种设计模式来展开说明一下。
Binder通信的数据流转如下图所示:
proxy-stub 设计模式¶
Proxy将特殊性接口转换成通用性接口,Stub将通用性接口转换成特殊性接口,二者之间的数据转换通过Parcel(打包)进行的,Proxy常作为数据发送代理,通过Parcel将数据打包发送,Stub常作为数据接收桩,解包并解析Parcel Data package。
举个例子
要做的事:我现在要开空调 Client(客户端):我 Proxy(代理):遥控器 Stub(实体):空调, Parcel(数据):遥控器传给空调的蓝牙、红外参数。
我按下了遥控器的提升温度按键,遥控器之前跟空调做了绑定,可以拿到空调的对象实体Stub,把按键的操作组装成一个Parcel数据,发给空调(Server),空调(Server)拿到请求后,执行相应的处理-提升温度,结果返回给遥控器。这样就完成了Proxy-Stub的数据交互流程。
总结¶
- AIDL要是实现的最终目标是跨进程访问,简单的说就是得到另一个进程的对象,并调用其方法。
- AIDL与接口类似,本质属性都是一个Interface(AIDL文件是IInterface,而Interface是继承自Interface的),而且都只定义了抽象方法,没有具体的实现,需要子类去实现。
- 与接口不同的是:由AIDL生成的stub类本质上是一个Binder!这个类所生成的对象有两种方式可以传递给另外一个进程:
- 一种是通过bindService的方式,绑定一个服务,而在绑定后,服务将会返回给客户端一个Binder的对象,此时可以把继承自stub的Binder传递给客户端。
- 另外一种就是把继承自stub的类提升为系统服务,此时,我们通过ServiceManager去得到当前的系统服务,ServiceManager就会把目标Service的Binder对象传递给客户端。
- 经过上面两种方法得到的Binder对象,就像得到了本地的某个对象一样,可以调用其远程的方法。
扩展知识¶
aidl 返回值¶
// IPerformanceService.aidl
package com.rtfsc.aidldemo;
interface IPerformanceService {
void setThreadPriority(int tid, int priority);
int getThreadPriority(int pid);
}
setThreadPriority没有返回值,getThreadPriority有返回值;我们来对比 Proxy 对于 setThreadPriority、getThreadPriority 方法的实现如下:
@Override
public void setThreadPriority(int tid, int priority) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(tid);
_data.writeInt(priority);
boolean _status = mRemote.transact(Stub.TRANSACTION_setThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setThreadPriority(tid, priority);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public int getThreadPriority(int pid) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
int _result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(pid);
boolean _status = mRemote.transact(Stub.TRANSACTION_getThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
return getDefaultImpl().getThreadPriority(pid);
}
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
return _result;
}
可以看到,调用 transact 时都传 reply 进去了。
笔者曾经跟别人争论过这个问题,有些人是认为没有返回值的函数是不需要传 reply 进去的。
oneway¶
// IPerformanceService.aidl
package com.rtfsc.aidldemo;
interface IPerformanceService {
oneway void setProcessPriority(int pid, int priority);
void setThreadPriority(int tid, int priority);
}
setProcessPriority 是 oneway,setThreadPriority 不是 oneway。
@Override
public void setProcessPriority(int pid, int priority) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(pid);
_data.writeInt(priority);
boolean _status = mRemote.transact(Stub.TRANSACTION_setProcessPriority, _data, null, android.os.IBinder.FLAG_ONEWAY);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setProcessPriority(pid, priority);
return;
}
} finally {
_data.recycle();
}
}
@Override
public void setThreadPriority(int tid, int priority) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeInt(tid);
_data.writeInt(priority);
boolean _status = mRemote.transact(Stub.TRANSACTION_setThreadPriority, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().setThreadPriority(tid, priority);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
这里刻意找了个也没有返回值的非oneway接口来做对比,在非oneway接口了,调用 transact 时传 reply 进去了。
而oneway接口是不需要传reply 进去的。并且oneway接口设置的flags是 android.os.IBinder.FLAG_ONEWAY 。
指定定向tag¶
两个进程中要传递的对象必须实现**Parcelable**接口,AIDL中序列化的对象传递还必须指定定向tag,tag表示数据的流通方向。 如下 copyArrayIn 指定的入参tag为 in 、如下 copyArrayOut 指定的入参tag为 out 、如下 copyArrayInOut 指定的入参tag为 inout 。
// IPerformanceService.aidl
package com.rtfsc.aidldemo;
interface IPerformanceService {
void copyArrayIn(in String[] source);
void copyArrayOut(out String[] source);
void copyArrayInOut(inout String[] source);
}
Proxy 对于 这三个方法的实现如下:
@Override
public void copyArrayIn(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStringArray(source);
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayIn, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayIn(source);
return;
}
_reply.readException();
} finally {
_reply.recycle();
_data.recycle();
}
}
@Override
public void copyArrayOut(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
if ((source == null)) {
_data.writeInt(-1);
} else {
_data.writeInt(source.length);
}
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayOut, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayOut(source);
return;
}
_reply.readException();
_reply.readStringArray(source);
} finally {
_reply.recycle();
_data.recycle();
}
}
Stub类的onTransact方法里:
case TRANSACTION_copyArrayOut: {
data.enforceInterface(descriptor);
java.lang.String[] _arg0;
int _arg0_length = data.readInt();
if ((_arg0_length < 0)) {
_arg0 = null;
} else {
_arg0 = new java.lang.String[_arg0_length];
}
this.copyArrayOut(_arg0);
reply.writeNoException();
reply.writeStringArray(_arg0);
return true;
}
在 copyArrayIn 函数中,创建Parcel对象_data,将 in 型参数source写入_data,完成序列化。
在 copyArrayOut 函数中,创建Parcel对象_data,写入source.length,调用transact()方法,等待Service端onTransact()响应。 onTransact() 得到String[]的长度;创建String[]变量,并从data中反序列化,得到client端传来的String[]变量;调用本地的 copyArrayOut 方法。 将输出的String[]型_arg0写入Parcel型reply变量,完成序列化;此时onTransact执行完毕。 Proxy将onTransact返回的_reply反序列化,写入source中,source即获得了返回值。整个流程结束。
所以指定 in:client端将参数设定好,传给server,server端拷贝、使用。 所以指定 out:client将接口、地址等“空壳”传给server,server将运算结果填充进去,然后client从中取出运算结果。
@Override
public void copyArrayInOut(java.lang.String[] source) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStringArray(source);
boolean _status = mRemote.transact(Stub.TRANSACTION_copyArrayInOut, _data, _reply, 0);
if (!_status && getDefaultImpl() != null) {
getDefaultImpl().copyArrayInOut(source);
return;
}
_reply.readException();
_reply.readStringArray(source);
} finally {
_reply.recycle();
_data.recycle();
}
}
对比 copyArrayInOut 和 copyArrayIn、copyArrayOut的区别可以知道;如果指定tag为 inout,那么client端既从此处传入数据,又从此处取出server填充的结果。
总结: 需要特别注意的是,对于非基本数据类型,也不是String和CharSequence类型的,需要有方向指示,包括in、out和inout。 in:client端将参数设定好,传给server,server端拷贝、使用。 out:client将接口、地址等“空壳”传给server,server将运算结果填充进去,然后client从中取出运算结果。 inout:client端既从此处传入数据,又从此处取出server填充的结果。
将完成in功能的设成out、或out功能的设成in,都会报错。若将连个均设成inout,则通过,但增加了序列化的步骤,白白浪费运算资源。