与Sevice实现双向通信(二)
来源:http://www.race604.com/communicate-with-remote-service-2/
这是系列文章《与Sevice实现双向通信》的第二篇。有了上一篇文章作为基础,本文实现一个稍微复杂一点的场景:
Service端实现一个控制中心(例如一个多人游戏),客户端可以随时加入,或者退出,每个客户端都可以获取当前参与进来的成员列表。
根据需求,在上一篇文章的代码的基础上,我们可以很容易申明如下接口:
// IRemoteService.aidlpackage com.race604.servicelib;interface IRemoteService {
...
void join(String userName);
void leave(String userName);
List<String> getParticipators();}
Service的实现也很简单,大致如下:
// RemoteService.javapackage com.race604.remoteservice;import ...public class RemoteService extends Service {
private List<String> mClients = new ArrayList<>();
private final IRemoteService.Stub mBinder = new IRemoteService.Stub() {
@Override public void join(String name) throws RemoteException {
mClients.add(name);
}
@Override public void leave(String name) throws RemoteException {
mClients.remove(name);
}
@Override public List<String> getParticipators() throws RemoteException {
return mClients;
}
};...
这里的实现非常简单,看起来也没有问题。
客户端的实现我这里就不写了。我们期望Client调用join()
和leave()
成对出现。在离开的时候,注意调用leave()
。
但是,考虑一个情况:客户端意外退出。例如客户端因为错误应用Crash,或者被Kill掉了。没有机会调用到leave()
。这样Service中的mClients
中的还保持这个客户端的信息,得不到释放。这里还是一个简单的例子,但是如果Service中如果为Client申请了一些资源,客户端意外退出以后,Service中资源得不到释放,会造成资源浪费。
幸运的是,Binder有可以让对端的进程得到意外退出通知的机制:Link-To-Death。我这里以我们这里Service被通知Client意外退出的情况为例,实现的方法如下:
-
Client传递一个Binder对象给Service,此Binder对象与Client的进程关联;
-
在Sevice中接受到这个Binder对象,并且使用
binder.linkToDeath()
,注册一个DeathRecipient
回调; -
实现
DeathRecipient
。当Client意外退出的时候,DeathRecipient.binderDied()
将被回调,我们可以在这里释放相关的资源。
具体实现如下: 修改AIDL的定义如下:
// IRemoteService.aidlpackage com.race604.servicelib;interface IRemoteService {
...
void join(IBinder token, String name);
void leave(IBinder token);
List<String> getParticipators();}
注意到这里接口中传入了一个IBinder
对象token
,此就是客户端的唯一标示。
接下来重点看一下Service的实现。我们首先定义个类来保存Client的信息,如下:
private final class Client implements IBinder.DeathRecipient {
public final IBinder mToken;
public final String mName;
public Client(IBinder token, String name) {
mToken = token;
mName = name;
}
@Override public void binderDied() {
// 客户端死掉,执行此回调 int index = mClients.indexOf(this);
if (index < 0) {
return;
}
Log.d(TAG, "client died: " + mName);
mClients.remove(this);
}}
这里为了方便,因为每个IBinder都需要注册一个IBinder.DeathRecipient
回调,我们就直接让Client实现此接口。
Service中保存客户端的信息也做如下修改:
private List<Client> mClients = new ArrayList<>();// 通过IBinder查找Clientprivate int findClient(IBinder token) {
for (int i = 0; i < mClients.size(); i++) {
if (mClients.get(i).mToken == token) {
return i;
}
}
return -1;}
然后修改join()
的实现如下:
@Overridepublic void join(IBinder token, String name) throws RemoteException {
int idx = findClient(token);
if (idx >= 0) {
Log.d(TAG, "already joined");
return;
}
Client client = new Client(token, name);
// 注册客户端死掉的通知 token.linkToDeath(client, 0);
mClients.add(client);}
注意到这里的token.linkToDeath(client, 0);
,表示的含义就是与token(IBinder对象)关联的客户端,如果意外退出,就会回调client.binderDied()
方法。
同理leave()
的实现如下:
@Overridepublic void leave(IBinder token) throws RemoteException {
int idx = findClient(token);
if (idx < 0) {
Log.d(TAG, "already left");
return;
}
Client client = mClients.get(idx);
mClients.remove(client);
// 取消注册 client.mToken.unlinkToDeath(client, 0);}
当调用leave的时候,释放相关资源,取消IBinder.DeathRecipient
回调,即client.mToken.unlinkToDeath(client, 0);
。
客户端调用就比较简单了,主要代码如下:
package com.race604.client;import ...public class MainActivity extends ActionBarActivity {
...
private IBinder mToken = new Binder();
private boolean mIsJoin = false;
private void toggleJoin() {
if (!isServiceReady()) {
return;
}
try {
if (!mIsJoin) {
String name = "Client:" + mRand.nextInt(10);
mService.join(mToken, name);
mJoinBtn.setText(R.string.leave);
mIsJoin = true;
} else {
mService.leave(mToken);
mJoinBtn.setText(R.string.join);
mIsJoin = false;
}
} catch (RemoteException e) {
e.printStackTrace();
}
}}
至此,核心代码都实现了。完整的代码请参考这个Commit。
我们做如下测试,在客户端join()后,然后在最近任务列表中,删除client应用,我们看到service端打印信息:
02-04 14:01:23.627: D/RemoteService(29969): client died: Client:6
可见,我们Kill掉客户端,回调到了这里:
private final class Client implements IBinder.DeathRecipient {
...
@Override public void binderDied() {
// 客户端死掉,执行此回调 ...
Log.d(TAG, "client died: " + mName);
...
}}
参考文章: