[Android IPC] AIDL 的使用(二)

关于 AIDL 的介绍以及基本使用请参看上一篇,本篇主要记录 AIDL 使用中的一些注意事项以及一些高级用法。

使用监听

还是接着上一篇博客中的例子,如果想要在客户端被动的接收一些数据,即服务端数据发生变化后通知客户端——观察者模式。

下面就以增加了新书然后通知客户端作为例子来实现:

首先肯定要定义一个接口起名叫 IOnNewBookAddedListener.aidl;

1
2
3
4
5
6
7
8
9
10
// IOnNewBookAdded.aidl
package com.maintel.binderdemo1.listener;
//手动引入 Book
import com.maintel.binderdemo1.model.Book;
interface IOnNewBookAddedListener {
void onNewBookAdded(in Book newBook);
}

然后再 IBookManager.aidl 中增加两个方法用来注册和取消注册;

1
2
3
4
5
6
7
8
9
10
11
package com.maintel.binderdemo1;
import com.maintel.binderdemo1.model.Book;
import com.maintel.binderdemo1.listener.IOnNewBookAddedListener;
interface IBookManager {
List<Book> getBookList();
void addBook(in Book book);
void registerListener(IOnNewBookAddedListener listener);
void unregisterListener(IOnNewBookAddedListener listener);
}

然后改造服务端实现新增加的方法:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
public class BookManagerService extends Service {
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();
private RemoteCallbackList<IOnNewBookAddedListener> mListeners = new RemoteCallbackList<>(); //注意这里
private boolean isServiceDestroyed = false;
@Override
public void onCreate() {
super.onCreate();
...
new Thread(new ServiceWorker()).start();
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
private Binder mBinder = new IBookManager.Stub() {
...
@Override
public void registerListener(IOnNewBookAddedListener listener) throws RemoteException {
mListeners.register(listener);
}
@Override
public void unregisterListener(IOnNewBookAddedListener listener) throws RemoteException {
mListeners.unregister(listener);
}
}
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!isServiceDestroyed) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int id = mBookList.size() + 1;
Book b = new Book(id, "new book*" + id);
try {
onBookAdded(b);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
private void onBookAdded(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListeners.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookAddedListener listener = mListeners.getBroadcastItem(i);
if (listener != null) {
listener.onNewBookAdded(book);
}
}
mListeners.finishBroadcast();
}
@Override
public void onDestroy() {
isServiceDestroyed = true;
super.onDestroy();
}
}

修改客户端如下:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public class MainActivity extends AppCompatActivity {
private IBookManager bookManager;
private IOnNewBookAddedListener onNewBookAddedListener = new IOnNewBookAddedListener.Stub() {
@Override
public void onNewBookAdded(Book newBook) throws RemoteException {
System.err.println(Thread.currentThread()); //将当前线程打印出来
Log.d("MainActivity$newbook", newBook.toString());
}
};
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName componentName, IBinder iBinder) {
Log.e("MainActivity$Connected", "Thread.currentThread():" + Thread.currentThread());//将当前线程打印出来
bookManager = IBookManager.Stub.asInterface(iBinder);
try {
bookManager.registerListener(onNewBookAddedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
@Override
public void onServiceDisconnected(ComponentName componentName) {
Log.e("MainActivity$Disconnect", "Thread.currentThread():" + Thread.currentThread());//将当前线程打印出来
bookManager = null;
}
};
@Override
protected void onDestroy() {
if (bookManager != null && bookManager.asBinder().isBinderAlive()) {
try {
bookManager.unregisterListener(onNewBookAddedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
unbindService(mConnection);
super.onDestroy();
}
}

以上就实现了一个简单的监听,每隔五秒会打印一条书籍的信息。

接下来就要说一些注意事项了,通过上面的代码可以发现在记录 IOnNewBookAddedListener 监听集合的时候使用了 RemoteCallbackList,而不是一般常用的 ArrayList 等为什么呢?

RemoteCallbackList

虽然常用的注册与解注册方法是传递相同的对象,而且上面也确实传递了同样的对象,但是首先要明白 AIDL 是用来实现多进程通讯的,其底层的 Binder 机制会把客户端传递过来的对象重新转化成一个新的对象,因此即使我们传递了相同的对象来解注册,但是到服务端的时候已经是一个新的对象了。这也是为什么使用自定义的对象的时候必须要实现 Parcelable 接口的原因。

RemoteCallbackList 是系统专门提供用于删除跨进程 listener 的接口,其内部维护了一个 Map 结构来保存所有的 AIDL 回调。

1
ArrayMap<IBinder,Callback> mCallbacks = new ArrayMap<IBinder,Callback>();

另外 RemoteCallbackList 在客户端进程终止后,会自动清除客户端注册的 listener,而且其内部实现了线程同步功能。

这里要注意的是遍历 RemoteCallbackList 的方法,只能使用下面的方法:

1
2
3
4
5
6
7
8
9
10
int i = callbacks.beginBroadcast();
while (i > 0) {
i--;
try {
callbacks.getBroadcastItem(i); // 获取回调
} catch (RemoteException e) {
}
}
callbacks.finishBroadcast();

beginBroadcast() 和 finishBroadcast() 一定要配合使用。

注意 listener 回调所在线程

当服务端回调 listener 中的方法时,被回调的方法运行在客户端的 Binder 线程池中,所以当回调需要操作 UI 时记得将线程切换到 UI 线程,同时,如果客户端回调方法要执行大量耗时操作时,会阻塞服务端发起回调的方法,因此要确保当客户端回调方法执行耗时操作是,服务端发起回调的方法不要运行在 UI 线程中。就像例子中 BookManagerService 的 onBookAdded 方法,它调用了客户端的 IOnNewBookAddedListener 的 onNewBookAdded 方法,如果客户端的这个 onNewBookAdded 方法比较耗时的话,要确保 onBookAdded 方法运行在非 UI 线程中。

注意耗时操作

由于服务端方法本身运行在服务端的 Binder 线程池中,所以服务端的方法本身可以执行大量耗时操作,但是如果在客户端的 UI 线程中调用服务端的耗时方法的话就可能触法 ANR 异常,因为当客户端调用服务端方法时客户端的现成会被挂起直到服务端执行完毕

所以当需要调用服务端的耗时方法时,客户端请务必放在子线程中操作。

死亡监听

可以有两个方法对 Service 的异常结束做监听。

  • 给 Binder 添加 DeathRecipient 监听,当 Binder 死亡时会收到 binderDeid 回调

    此方法运行在 Binder 线程池中。

  • 在 onServiceDisconnected 中做监听,当 Service 异常结束时会调用此方法

    此方法运行在 UI 线程中。

权限检查

  • 可以通过自定义权限然后在 onBind 中进行权限验证;
  • 通过自定义权限在 onTransact 中进行验证;
  • 通过 Uid 和Pid 在 onTransact 中进行验证;
  • 为 Service 制定 android:permission 属性来验证;

参考资料