1.in、out、inout、oneway关键字
- 定向tag in修饰的的参数,经序列化后传递服务端,服务端反序列化得到一个与之值相同的新的对象;
- 定向tag out修饰的参数,客户端不会序列化该参数,而是服务端调用无参构造方法新建了一个对象,待目标方法返回后,将参数写入reply返回给客户端;
- 定向tag inout基本上算是in、out的并集,为什么说基本上,因为out会在服务端通过new关键字来新建一个对象,而inout已经通过反序列化客户端传过来的数据得到一个新的对象,就没有别要再new一个了。
- oneway:正常情况下Client调用AIDL接口方法时会阻塞,直到Server进程中该方法被执行完。oneway可以修饰AIDL文件里的方法,oneway修饰的方法在用户请求相应功能时不需要等待响应可直接调用返回,非阻塞效果,该关键字可以用来声明接口或者声明方法,如果接口声明中用到了oneway关键字,则该接口声明的所有方法都采用oneway方式。(注意,如果client和Server在同一进程中,oneway修饰的方法还是会阻塞)。
2.AIDL默认支持的数据类型
- Java的所有基本数据类型,只支持定向tag in;
- String,只支持定向tag in;
- CharSequence,只支持定向tag in;
- List,其中元素必须是aidl支持的数据类型,包括1,2,3和其他aidl文件定义的parcelable;另外还有一点,接收方得到的总是ArrayList;
- Map,其中元素必须是aidl支持的数据类型,包括1,2,3和其他aidl文件定义的parcelable;另外还有一点,接收方得到的总是HashMap。
3.Binder死亡代理
在进程间通信过程中,很可能出现一个进程死亡的情况。如果这时活着的一方不知道另一方已经死了就会出现问题。那我们如何在A进程中获取B进程的存活状态呢?
android肯定给我们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法需要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,我们就可以在binderDied方法里面做一些异常处理,释放资源等操作了。示例如下:
...
mClientCallBack = IRemoteCallBack.Stub.asInterface(callback);
if (mClientDeathHandler == null) {
mClientDeathHandler = new ClientDeathRecipient();
}
mClientCallBack.asBinder().linkToDeath(new ClientDeathRecipient(), 0);
...
private class ClientDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
mCallbackList.unregister(mClientCallBack);
mClientCallBack = null;
Logger.d(TAG,"client is died");
}
}
4. Client注册回调接口
通过AIDL在server端设置一个client的回调。这样的话就相当于client端是server端的server了。但是在client调用解注册时,由于client和server在不同进程,对象无法跨进程传输,虽然在client端调用解注册方法,传入的是同一个回调对象,但在server端反序列化后会生成两个全新的对象,最后在server端自然无法反注册成功。
可以使用android提供的 RemoteCallbackList类来管理传入的回调接口对象, 其可以实现正常注册于解注册的原因在于注册与解注册时虽然对应的回调接口对象不是同一个,但是其对应的底层Binder对象却是同一个。
...
mClientCallBack = IRemoteCallBack.Stub.asInterface(callback);
if (mClientDeathHandler == null) {
mClientDeathHandler = new ClientDeathRecipient();
}
mClientCallBack.asBinder().linkToDeath(new ClientDeathRecipient(), 0);
...
private class ClientDeathRecipient implements IBinder.DeathRecipient {
@Override
public void binderDied() {
mCallbackList.unregister(mClientCallBack);
mClientCallBack = null;
Logger.d(TAG,"client is died");
}
}
上面是我在server端对client的回调接口的binder对象设置的DeathRecipient。在client死亡时,解注册client的回调,并且置空。
client注册回调接口
之前一直说的都是client向server的通信,那如果server要调用client呢?
一个比较容易想到的办法就是通过AIDL在server端设置一个client的回调。这样的话就相当于client端是server端的server了。
有注册回调就肯定有解注册,但是client端与server不在一个进程,server是无法得知client解注册时传入的回调接口是哪一个(client调用解注册时,是通过binder传输到server端,所以解注册时的回调接口是新创建的,而不是注册时的回调接口)。为了解决这个问题,android提供了RemoteCallbackList这个类来专门管理remote回调的注册与解注册。
用法如下:
//TaskCallback.aidl 用于存放要回调client端的方法package com.xns.demo.server;
interface ITaskCallback {
void actionPerformed(int actionId);
}
//ITaskBinder.aidl 用于存放供给client端调用的方法package com.xns.demo.server;
import com.xns.demo.server.ITaskCallback;
interface ITaskBinder {
boolean isTaskRunning();
void stopRunningTask();
void registerCallback(ITaskCallback cb);
void unregisterCallback(ITaskCallback cb);
}
//myservicepackage com.xns.demo.server;
import com.xns.demo.server.ITaskBinder;
import com.xns.demo.server.ITaskCallback;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.util.Log;
public class MyService extends Service {
private static final String TAG = "aidltest";
...
@Override
public IBinder onBind(Intent t) {
printf("service on bind");
return mBinder;
}
@Override
public boolean onUnbind(Intent intent) {
printf("service on unbind");
return super.onUnbind(intent);
}
void callback(int val) {
final int N = mCallbacks.beginBroadcast();
for (int i=0; i<N; i++) {
try {
mCallbacks.getBroadcastItem(i).actionPerformed(val);
}
catch (RemoteException e) {
// The RemoteCallbackList will take care of removing
// the dead object for us.
}
}
mCallbacks.finishBroadcast();
}
private final ITaskBinder.Stub mBinder = new ITaskBinder.Stub() {
public void stopRunningTask() {
}
public boolean isTaskRunning() {
return false;
}
public void registerCallback(ITaskCallback cb) {
if (cb != null) {
mCallbacks.register(cb);
}
}
public void unregisterCallback(ITaskCallback cb) {
if(cb != null) {
mCallbacks.unregister(cb);
}
}
};
final RemoteCallbackList <ITaskCallback>mCallbacks = new RemoteCallbackList <ITaskCallback>();
}
//client端package com.xns.demo;
...
import com.xns.demo.server.*;
public class MyActivity extends Activity {
private static final String TAG = "aidltest";
private Button btnOk;
private Button btnCancel;
...
ITaskBinder mService;
private ServiceConnection mConnection = new ServiceConnection() {
public void onServiceConnected(ComponentName className, IBinder service) {
mService = ITaskBinder.Stub.asInterface(service);
try {
mService.registerCallback(mCallback);
} catch (RemoteException e) {
}
}
public void onServiceDisconnected(ComponentName className) {
mService = null;
}
};
private ITaskCallback mCallback = new ITaskCallback.Stub() {
public void actionPerformed(int id) {
printf("callback id=" + id);
}
};
}
5.其他
- 在使用自定义Parcelable类型时,必须使用import语句进行导入,即使是在同一个包中。
- aidl接口中只能包含方法的定义,不能声明静态字段。
- aidl客户端调用远程服务的方法时,当前线程会被挂起,直至服务端方法执行完成。因此要避免在UI线程调用远程服务方法。
- 使用aidl时 ,service每收到一个client端的请求时就在Binder线程池中取一个线程去执行相应操作。因此在服务端做数据处理时应注意线程安全的问题。
网友评论