Android AIDL注意点
如果AIDL Server 出现了崩溃 (不是AIDL调用的方法) 的情况下,Client可通过死亡代理(IBinder.DeathRecipient)注册监听,进行重新远程绑定Service
private IBinder.DeathRecipient deathRecipient = new IBinder.DeathRecipient() {
@Override
public void binderDied() {
if (bookManager == null) {
return;
}
bookManager.asBinder().unlinkToDeath(deathRecipient, 0);
bookManager = null;
//TODO:这里重写绑定远程Service
bindMyBookService();
}
};
在ServiceConnection
private ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
bookManager = IBookManager.Stub.asInterface(iBinder);
try {
iBinder.linkToDeath(deathRecipient, 0);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
bookManager = null;
}
};
deathRecipient和在ServiceConnection的onServiceDisconnected中处理的效果相同,但deathRecipient运行在Binder连接池中,onServiceDisconnected运行在Client UI线程中。
为空判断
如果AIDL Server没有安装,Client进行远程绑定的时候并不会报错。但是此时binder的实例是为null的,所以对于这个,要进行为空判断,如果为空,需进行重新绑定。
if (bookManager == null) {
Toast.makeText(MainActivity.this, "aidl已断开", Toast.LENGTH_SHORT).show();
return;
}
可通过Binder的isBinderAlive判断Binder是否死亡。
手动导包
尽管Book类已经和IBookManager位于相同的包中,但是在IBookManager中仍然要导入Book类,这就是AIDL的特殊之处。
AIDL Client请求后会挂起
当客户端发起远程请求时,由于当前线程会被挂起直至服务端进程返回数据,所以如果一个远程方法是很耗时的,那么不能在UI线程中发起此远程请求。
AIDL Binder方法应采用同步的方式
由于服务端的Binder方法运行在Binder的线程池中,所以Binder方法不管是否耗时都应该采用同步的方式去实现
AIDL文件不是实现Binder的必须品
我们完全可以不提供AIDL文件即可实现Binder,之所以提供AIDL文件,是为了方便系统为我们生成代码
AIDL支持的数据类型
- 基本数据类型(int、long、char、boolean、double等)
- String和CharSequence
- List:只支持ArrayList,里面的每个元素都必须能被AIDL支持
- Map:只支持HashMap,里面的每个元素都必须被AIDL支持,包括key和value
- Parcelable:所有实现了Parcelable接口的对象
- AIDL:所有的AIDL接口本身也可以在AIDL文件中使用
自定义的Parcelable对象和AIDL对象必须要显式import进来
如果AIDL文件中用到了自定义的Parcelable对象,那么必须新建一个和它同名的AIDL文件,并在其中声明它为Parcelable类型。
Book.java
package com.ryg.chapter_2.aidl;
parcelable Book;
除了基本数据类型,其他类型的参数必须标上方法:in、out或者input
in:输入型参数
out:输出型参数
inout:输入输出型参数
AIDL接口中只支持方法,不支持声明静态常量
为方便AIDL的开发,建议把所有和AIDL相关的类和文件放入一个lib中
如要使用,导入该lib即即可,同时,维护也方便。
AIDL的包结构在服务端和客户端要保持一致
否则运行会出错,这是因为客户端需要反序列化服务端中和AIDL接口相关的所有类,如果类的完整路径不一样的话,就无法成功反序列化,程序也就无法正常运行。
CopyOnWriteArrayList
支持并发读/写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形
所以我们要在AIDL方法中处理线程同步,而这里我们直接使用CopeOnWriteArrayList来进行自动的线程同步
类似的还有ConcurrentHashMap
如果某个远程方法是耗时的,那么就要避免客户端的UI线程中去访问远程方法
客户端调用远程服务的方法,被调用的方法运行在服务端的Binder线程池中,同时客户端线程被挂起,这个时候服务端方法若比较耗时,在UI线程执行该方法就可能导致ANR
异常处理
在Binder调用过程中,即使服务端抛出异常,服务端也不会挂 (服务端中系统默认是对异常进行了try异常捕获的处理,所以挂不了)。
IBookManager.aidl的实现类IBookManager.java
@Override
public void addBook(com.ethanco.aidlservice.bean.Book book) throws android.os.RemoteException {
android.os.Parcel _data = android.os.Parcel.obtain();
android.os.Parcel _reply = android.os.Parcel.obtain();
try {
//.....
} finally {
_reply.recycle();
_data.recycle();
}
}
- 抛出的异常有些能够传到客户端,比如NullPointException,这时客户端就会挂
- 但有些异常不能传到客户端,比如RuntimeException,这时,客户端也不会挂
如果Binder调用过程中,服务端的代码是创建了新的线程中抛出异常(在这个线程中没有进行异常捕获处理),服务端会挂,客户端不会挂。
权限检查
权限检查不能在服务端 Service的onBind方法内去检查
@Override
public IBinder onBind(Intent intent) {
//check authority 在此处无效
}
因为在这里检查权限,是运行在服务端上的UI线程,不是Binder调用,检查的是服务端的权限。。。
如需进行权限检查,应在
@Override
public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
//此处可进行权限检查
String[] packages = getPackageManager().getPackagesForUid(getCallingUid());
String packageName = null;
if (packages != null && packages.length > 0) {
packageName = packages[0];
}
Log.i(TAG, "packageName:" + packageName);
return super.onTransact(code, data, reply, flags);
}
onTrasact方法是运行在服务端的Binder线程池里,是Bind调用。如上面的代码,打印出的包名会是客户端的包名,即调用者的包名是客户端。
Binder连接池
将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免重复创建Service的过程
将所有的AIDL放在同一个Service中去管理。
BinderPool能够极大提高AIDL的开发效率,并且可以避免大量的Service创建。因此,建议在AIDL开发时使用BinderPool机制。