由于笔者未卜先知,知道自己将写一篇关于 AIDL
的技术分析文章
于是两年前笔者写了这个项目 IPC_Sample
(手动滑稽 ~~)
两年后的现在,我在这里把这篇文章补上
AIDL 实现步骤
这里不会介绍具体的编写步骤,如果需要可以参考 Android 接口定义语言 (AIDL)
具体编写好的代码请参考上述项目 IPC_Sample
总体可以分为三步:
- 编写
.aidl
文件 - 实现服务端
RemoteService
- 客户端调用服务端的方法
编写.aidl
文件时有以下几点需要注意:
- 支持的数据类型有限
- 所有非原语参数都需要指示数据走向的方向标记
可以是 in、out 或 inout
原语默认为 in,不能是其他方向
应该将方向限定为真正需要的方向,因为编组参数的开销极大 - 只支持方法
AIDL
文件并不是必需的,只是有了这个文件,系统会帮助我们自动生成 IRemoteService.java
这个类
AIDL
的包结构在服务端和客户端要保持一致,否则反序列化将会失败!
onTransact()
方法在 Binder
线程池中执行,如果要进行 UI 操作,可以利用 Handler
切换到主线程执行
客户端挂起直到服务端返回,所以要注意避免 ANR
IRemoteService
下面的分析还是以这个项目为例进行讲解 IPC_Sample
当我们写完 .aidl
文件后,AS 会自动在 build
子目录下为我们生成 IRemoteService
,这个接口的具体结构如下:
public interface IRemoteService extends android.os.IInterface{
public static abstract class Stub extends android.os.Binder implements com.sbingo.ipc_sample.AIDL.IRemoteService {
……
private static class Proxy implements com.sbingo.ipc_sample.AIDL.IRemoteService {
……
}
}
public int getPid() throws android.os.RemoteException;
public int add(int a, int b) throws android.os.RemoteException;
public void basicTypes(int anInt, long aLong, boolean aBoolean, float aFloat, double aDouble, java.lang.String aString) throws android.os.RemoteException;
}
理解了这个类的结构, AIDL
的原理就基本理解了
Stub
static abstract
抽象内部类,继承了 Binder
,实现了 IRemoteService
这是服务端,如果客户端和服务端在同一进程,Stub
对象将被返回给客户端
Proxy
static
内部类的代理类,实现了 IRemoteService
如果客户端和服务端不在同一进程,代理对象将被返回给客户端
流程解析
现在我们来分析整个流程,搞懂背后的原理
客户端持有服务端引用
客户端在成功连接服务端后的代码如下:
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
mService = IRemoteService.Stub.asInterface(service); //1
mCallbackText.setText("已连接服务端");
mIsBound = true;
}
注释1处调用了 IRemoteService.Stub
的 asInterface()
方法,跟进去继续看:
/**
* Cast an IBinder object into an com.sbingo.ipc_sample.AIDL.IRemoteService interface,
* generating a proxy if needed.
*/
public static com.sbingo.ipc_sample.AIDL.IRemoteService asInterface(android.os.IBinder obj) {
if ((obj == null)) {
return null;
}
android.os.IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
if (((iin != null) && (iin instanceof com.sbingo.ipc_sample.AIDL.IRemoteService))) {
return ((com.sbingo.ipc_sample.AIDL.IRemoteService) iin);
}
return new com.sbingo.ipc_sample.AIDL.IRemoteService.Stub.Proxy(obj);
}
这个方法的作用就是判断客户端和服务端是否为同一进程
是的话返回类 Stub
的实例
否则返回代理类 Proxy
的实例
客户端调用服务端方法
这里以 add()
方法为例讲解调用过程,并且假设客户端和服务端在不同的进程中
private void tryToAdd() {
if (!mIsBound) {
Toast.makeText(MainActivity.this, "请先绑定服务端", Toast.LENGTH_SHORT).show();
return;
}
if (TextUtils.isEmpty(num1.getText().toString()) ||
TextUtils.isEmpty(num2.getText().toString())) {
Toast.makeText(MainActivity.this, "请输入以上两个数字", Toast.LENGTH_SHORT).show();
} else {
try {
int r = mService.add(Integer.valueOf(num1.getText().toString()), Integer.valueOf(num2.getText().toString()));
mCallbackText.setText("相加结果:" + r);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
第11行是调用的代码,mService
就是之前保存的代理类 Proxy
的实例
于是我们查看代理类 Proxy
的 add()
方法
@Override
public int add(int a, int b) 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(a);
_data.writeInt(b);
//这里会阻塞等待 mRemote 的 onTransact() 返回的计算结果
mRemote.transact(Stub.TRANSACTION_add, _data, _reply, 0);
//计算结果保存在 _reply 中
_reply.readException();
_result = _reply.readInt();
} finally {
_reply.recycle();
_data.recycle();
}
//返回结果给客户端
return _result;
}
配合注释,这段代码很容易理解
第12行的 mRemote
就是之前的 asInterface()
方法传入的 IBinder
对象,在本项目中就是RemoteService.java
中传入的 Stub
实例
于是我们查看 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 TRANSACTION_getPid: {
data.enforceInterface(descriptor);
int _result = this.getPid();
reply.writeNoException();
reply.writeInt(_result);
return true;
}
case TRANSACTION_add: {
data.enforceInterface(descriptor);
//读入传入的两个参数
int _arg0;
_arg0 = data.readInt();
int _arg1;
_arg1 = data.readInt();
//调用服务端的具体实现方法
int _result = this.add(_arg0, _arg1);
//写入计算结果
reply.writeNoException();
reply.writeInt(_result);
return true;
}
……
default: {
return super.onTransact(code, data, reply, flags);
}
}
}
第20行的 this
指的是 RemoteService.java
中传入的 Stub
实例
public class RemoteService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override
public int getPid() {
return Process.myPid();
}
@Override
public int add(int a, int b) throws RemoteException {
return a + b;
}
@Override
public void basicTypes(int anInt, long aLong, boolean aBoolean,
float aFloat, double aDouble, String aString) {
// Does nothing
}
};
}
第17行就是真正执行计算的地方了,自此计算结果一路返回至客户端,调用成功!
系统服务 Binder 示例
系统服务中使用了大量的 Binder
这里以常见的 startActivity()
为例,它涉及到的 Binder
相关类如下
interface IActivityManager extends IInterface //接口
ServiceManager.getService("activity") //真正的服务提供者
abstract class ActivityManagerNative extends Binder implements IActivityManager // Binder 子类
class ActivityManagerProxy implements IActivityManager //代理类
可以看到,这些和之前分析的原理是一样的
有兴趣的可以自行查看源码,这里不再细述了
服务端死亡通知
如果服务端进程因为某种异常而终止了,它给客户端使用的功能就会受到影响
为解决这一问题,接口 IBinder
提供了死亡通知,可以在服务端终止时通知客户端,与此相关的两个方法如下:
/**
* 注册死亡通知
*/
linkToDeath()
/**
* 移除死亡通知
*/
unlinkToDeath()
服务端死亡时系统会回调客户端的 binderDied()
方法,此时应该移除之前绑定的 Binder
代理,然后重新绑定远程服务
当然,还有更简单的方法可以得知服务端已经挂了,那就是 onServiceDisconnected()
回调
这个方法和上述死亡通知的区别就是:onServiceDisconnected()
方法在主线程被回调,可以操作 UI
RemoteCallbackList
对象的跨进程传输本质上是反序列化的过程,所以 AIDL
中的自定义对象都要实现 Parcelable
接口,对象传输后得到的是不同的对象
正因为如此,如果客户端用常见写法在服务端设置了某种监听器,解注册时会失败,因为客户端传入的解注册对象在服务端并不存在!
为解决这一问题,系统提供了专门用于跨进程解注册的类 RemoteCallbackList<E extends IInterface>
它主要有以下三大功能:
- 成员变量
mCallbacks
为ArrayMap
类型,它的key
为IInterface
类型,轻松实现解注册功能 - 注册
listener
时同时注册死亡通知,所以客户端进程终止后,会自动移除客户端所注册的listener
- 内部自动实现了线程同步功能
它的使用方式主要涉及以下3个方法:
beginBroadcast()
getBroadcastItem()
finishBroadcast()
权限验证
如果服务端只允许有权限的客户端调用,可以增加权限验证
权限验证失败就无法调用我们的服务
在 onBind()
或 onTransact()
方法中进行 permission
验证