转载

与Sevice实现双向通信(二)

  1. 与Sevice实现双向通信(一)
  2. 与Sevice实现双向通信(二)
  3. 与Sevice实现双向通信(三) (coming soon)

这是系列文章《与Sevice实现双向通信》的第二篇。有了上一篇文章作为基础,本文实现一个稍微负责一点的场景:

Service端实现一个控制中心(例如一个多人游戏),客户端可以随时加入,或者退出,每个客户端都可以获取当前参与进来的成员列表。

根据需求,在上一篇文章的代码的基础上,我们可以很容易申明如下接口:

// IRemoteService.aidl package com.race604.servicelib; interface IRemoteService {    ...  void join(String userName);  void leave(String userName);  List<String> getParticipators(); }  

Service的实现也很简单,大致如下:

// RemoteService.java package 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意外退出的情况为例,实现的方法如下:

  1. Client传递一个Binder对象给Service,此Binder对象与Client的进程关联;
  2. 在Sevice中接受到这个Binder对象,并且使用 binder.linkToDeath() ,注册一个 DeathRecipient 回调;
  3. 实现 DeathRecipient 。当Client意外退出的时候, DeathRecipient.binderDied() 将被回调,我们可以在这里释放相关的资源。

具体实现如下: 修改AIDL的定义如下:

// IRemoteService.aidl package 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查找Client private int findClient(IBinder token) {    for (int i = 0; i < mClients.size(); i++) {   if (mClients.get(i).mToken == token) {    return i;   }  }  return -1; }  

然后修改 join() 的实现如下:

@Override public 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() 的实现如下:

@Override public 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);   ...  } }  
正文到此结束
Loading...