当前位置:   article > 正文

Android AIDL示例-RemoteCallbackList添加移除监听

remotecallbacklist

前言

AIDL是一个缩写,全称是Android Interface Definition Language,也就是Android接口定义语言,它是用来实现进程间通讯的,本文使用AIDL写一个小demo来实现夸进程间通讯 。

本文接着这一篇文章写,这篇文章有个留着的——就是远程AIDL服务端注册解绑Listener。

Android AIDL示例-回调方法版

先来看看服务端和客户端大体的AIDL-生成的java文件 。

如上图所示,蓝色框内是我们定义的AIDL文件,红色框内是自动编译生成的java文件。

服务端

这里只是个简单示意,接下来会在此基础上改。下面我们看下服务端的代码:

  1. /**
  2. * 音乐管理的服务类
  3. */
  4. public class MusicManagerService extends Service {
  5. private static final String TAG = MusicManagerService.class.getSimpleName();
  6. private ArrayList<Music> mMusicList = new ArrayList<>(); //生成的音乐列表
  7. private List<INewMusicArrivedListener> mListenerList = new ArrayList<>(); //客户端注册的接口列表
  8. private boolean isServiceDestroy = false; //当前服务是否结束
  9. private int num = 0;
  10. /**
  11. * 解绑服务
  12. * @param conn
  13. */
  14. @Override
  15. public void unbindService(ServiceConnection conn) {
  16. super.unbindService(conn);
  17. Log.e(TAG,"unbindService-----");
  18. }
  19. /**
  20. * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
  21. * 这个类需要实现IMusicManager相关的抽象方法
  22. */
  23. private Binder mBinder = new IMusicManager.Stub() {
  24. @Override
  25. public List<Music> getMusicList() throws RemoteException {
  26. return mMusicList;
  27. }
  28. @Override
  29. public void addMusic(Music music) throws RemoteException {
  30. mMusicList.add(music);
  31. }
  32. @Override
  33. public void registerListener(INewMusicArrivedListener listener) throws RemoteException {
  34. mListenerList.add(listener);
  35. int num = mListenerList.size();
  36. Log.e(TAG, "添加完成, 注册接口数: " + num);
  37. }
  38. @Override
  39. public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException {
  40. // 后文实现
  41. }
  42. };
  43. //新音乐到达后给客户端发送相关通知
  44. private void onNewMusicArrived(Music music) throws Exception {
  45. mMusicList.add(music);
  46. Log.e(TAG, "发送通知的数量: " + mMusicList.size());
  47. int num = mListenerList.size();
  48. for (int i = 0; i < num; ++i) {
  49. INewMusicArrivedListener listener = mListenerList.get(i);
  50. listener.onNewMusicArrived(music);
  51. }
  52. for (Music b : mMusicList){
  53. Log.e(TAG,b.name+" "+b.author);
  54. }
  55. }
  56. @Override public void onCreate() {
  57. super.onCreate();
  58. Log.e(TAG,"onCreate-------------");
  59. //首先添加两首歌曲
  60. mMusicList.add(new Music("《封锁我一生》", "王杰"));
  61. mMusicList.add(new Music("《稻香》", "周杰伦"));
  62. }
  63. @Override public void onDestroy() {
  64. isServiceDestroy = true;
  65. super.onDestroy();
  66. Log.e(TAG,"onDestroy-----");
  67. }
  68. @Nullable @Override
  69. public IBinder onBind(Intent intent) {
  70. return mBinder;
  71. }
  72. }

客户端

我们看下客户端的代码:这里需要注意,回调接口在客户端注册,目的是为了让服务端拿到回调接口,通知客户端。客户端拿到的是服务端的代理,注册回调可看作服务端收到回调listener。

  1. private TextView music_list;
  2. private IMusicManager mRemoteMusicManager; //音乐管理类 通过aidl文件编译生成的java类
  3. //监听新音乐的到达的接口
  4. private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() {
  5. /**
  6. * 服务端有新音乐生成
  7. * @param newMusic
  8. * @throws RemoteException
  9. */
  10. @Override
  11. public void onNewMusicArrived(Music newMusic) throws RemoteException {
  12. }
  13. };
  14. //绑定服务时的链接参数
  15. private ServiceConnection mConnection = new ServiceConnection() {
  16. @Override public void onServiceConnected(ComponentName name, IBinder service) {
  17. IMusicManager musicManager = IMusicManager.Stub.asInterface(service);
  18. try {
  19. mRemoteMusicManager = musicManager;
  20. Music newMusic = new Music("《客户端音乐》", "rock");
  21. musicManager.addMusic(newMusic);
  22. musicManager.registerListener(musicArrivedListener);
  23. } catch (RemoteException e) {
  24. e.printStackTrace();
  25. }
  26. }
  27. @Override public void onServiceDisconnected(ComponentName name) {
  28. mRemoteMusicManager = null;
  29. Log.e(TAG, "绑定结束");
  30. }
  31. };
  32. @Override
  33. protected void onCreate(Bundle savedInstanceState) {
  34. super.onCreate(savedInstanceState);
  35. setContentView(R.layout.activity_main);
  36. music_list = findViewById(R.id.music_list);
  37. }
  38. /**
  39. * 获取music列表
  40. *
  41. * @param view 视图
  42. */
  43. public void getMusicList(View view) {
  44. if (mRemoteMusicManager !=null){
  45. List<Music> list = null;
  46. try {
  47. list = mRemoteMusicManager.getMusicList();
  48. }catch (Exception e){
  49. }
  50. if (list!=null){
  51. String content = "";
  52. for (int i = 0; i < list.size(); ++i) {
  53. content += list.get(i).toString() + "\n";
  54. }
  55. music_list.setText(content);
  56. }
  57. }
  58. Toast.makeText(getApplicationContext(), "正在获取中...", Toast.LENGTH_SHORT).show();
  59. }
  60. /**
  61. * 绑定服务按钮的点击事件
  62. *
  63. * @param view 视图
  64. */
  65. public void bindService(View view) {
  66. Intent intent = new Intent(this, MusicManagerService.class);
  67. bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  68. }
  69. /**
  70. * 解绑服务
  71. */
  72. public void unbindService(View view){
  73. unbindService(mConnection);
  74. }

绑定一个服务 ,然后注册一个新音乐到达的接口,当服务端有新音乐生成的时候就会触发这个接口。

跨进程通信可能遇到问题

在IPC的过程当中,可能会遇到如下状况须要考虑:

在AIDL中客户端向服务端注册一个回调方法时,服务端要考虑客户端是否意外退出(客户端由于错误应用Crash,或者被Kill掉了),服务端还不知道,去回调客户端,出现错误。

客户端和服务端进程状态处理

在进程间通讯过程当中,极可能出现一个进程死亡的状况。若是这时活着的一方不知道另外一方已经死了就会出现问题。那咱们如何在A进程中获取B进程的存活状态呢?

android确定给咱们提供了解决方式,那就是Binder的linkToDeath和unlinkToDeath方法,linkToDeath方法须要传入一个DeathRecipient对象,DeathRecipient类里面有个binderDied方法,当binder对象的所在进程死亡,binderDied方法就会被执行,咱们就能够在binderDied方法里面作一些异常处理,释放资源等操作了。

Android SDK提供一个封装好的对象:RemoteCallbackList,帮我们自动处理了Link-To-Death的问题。

这里,简单介绍一下RemoteCallbackList:

public class RemoteCallbackList
extends Object
java.lang.Object
↳ android.os.RemoteCallbackList<E extends android.os.IInterface>

负责维护远程接口列表的繁琐工作,一般用于执行从Service到其客户端的回调 。特别是:

跟踪一组已注册的IInterface回调,通过其基础唯一性IBinder进行识别:IInterface#asBinder。

附加IBinder.DeathRecipient到每一个已注册的接口,以便在其过程消失时能够将其从列表中清除。

对接口的基础列表执行锁定以处理多线程传入的调用,并以线程安全的方式遍历该列表。

要使用此类,只需与服务一块儿建立一个实例,而后在客户端注册和取消注册服务时调用其register(E)和unregister(E)方法。回调到注册客户端,使用beginBroadcast(), getBroadcastItem(int)和finishBroadcast()。

当注册的回调在过程中消失了,该类将负责自动将其从列表中删除。若是要在这种状况下做其余工作,要建立一个实现该onCallbackDied(E)方法的子类。

RemoteCallbackList帮咱们避免了IPC两个进程在调用过程当中发生意外crash,致使回调失败或者进程crash的问题。

将上面服务端-客户端代码修改如下:

  1. /**
  2. * 音乐管理的服务类
  3. */
  4. public class MusicManagerService extends Service {
  5. private static final String TAG = MusicManagerService.class.getSimpleName();
  6. // 省略部分代码。。。
  7. private RemoteCallbackList<INewMusicArrivedListener> mListenerList = new RemoteCallbackList<>(); //客户端注册的接口列表
  8. //新音乐到达后给客户端发送相关通知
  9. private void onNewMusicArrived(Music music) throws Exception {
  10. mMusicList.add(music);
  11. mListenerList.beginBroadcast();
  12. //遍历全部注册的Listener,逐个调用它们的实现方法,也就是通知全部的注册者
  13. for(int i=0;i<mListenerList.getRegisteredCallbackCount();i++){
  14. INewMusicArrivedListener listener = mListenerList.getBroadcastItem(i);
  15. listener.onNewMusicArrived(music);
  16. }
  17. mListenerList.finishBroadcast();
  18. for (Music b : mMusicList){
  19. Log.e(TAG,b.name+" "+b.author);
  20. }
  21. }
  22. /**
  23. * 服务端通过Binder实现AIDL的IMusicManager.Stub接口
  24. * 这个类需要实现IMusicManager相关的抽象方法
  25. */
  26. private Binder mBinder = new IMusicManager.Stub() {
  27. // 省略部分代码。。。
  28. @Override
  29. public void registerListener(INewMusicArrivedListener listener) throws RemoteException {
  30. mListenerList.register(listener);
  31. }
  32. @Override
  33. public void unregisterListener(INewMusicArrivedListener listener) throws RemoteException {
  34. mListenerList.unregister(listener);
  35. }
  36. };
  37. }
  1. private IMusicManager mRemoteMusicManager; //音乐管理类 通过aidl文件编译生成的java类
  2. //监听新音乐的到达的接口
  3. private INewMusicArrivedListener musicArrivedListener = new INewMusicArrivedListener.Stub() {
  4. /**
  5. * 服务端有新音乐生成
  6. * @param newMusic
  7. * @throws RemoteException
  8. */
  9. @Override
  10. public void onNewMusicArrived(Music newMusic) throws RemoteException {
  11. }
  12. };
  13. //进程挂掉通知处理
  14. private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
  15. @Override
  16. public void binderDied() {
  17. if(mRemoteMusicManager != null){
  18. //Server端意外died
  19. try {
  20. mRemoteMusicManager.unRegistListener(musicArrivedListener);
  21. } catch (RemoteException e) {
  22. e.printStackTrace();
  23. }
  24. mRemoteMusicManager.asBinder().unlinkToDeath(mDeathRecipient,0);
  25. mRemoteMusicManager = null;
  26. //TODO:bindService again
  27. bindService()
  28. }
  29. }
  30. };
  31. //绑定服务时的链接参数
  32. private ServiceConnection mConnection = new ServiceConnection() {
  33. @Override public void onServiceConnected(ComponentName name, IBinder service) {
  34. mRemoteMusicManager = IMusicManager.Stub.asInterface(service);
  35. try {
  36. //设定死亡接收器,这个是针对IBinder对象的
  37. service.linkToDeath(mDeathRecipient,0);
  38. Music newMusic = new Music("《客户端音乐》", "rock");
  39. mRemoteMusicManager.addMusic(newMusic);
  40. mRemoteMusicManager.registerListener(musicArrivedListener);
  41. } catch (RemoteException e) {
  42. e.printStackTrace();
  43. }
  44. }
  45. @Override public void onServiceDisconnected(ComponentName name) {
  46. mRemoteMusicManager = null;
  47. Log.e(TAG, "绑定结束");
  48. }
  49. };
  50. /**
  51. * 绑定服务按钮的点击事件
  52. *
  53. */
  54. public void bindService() {
  55. Intent intent = new Intent(this, MusicManagerService.class);
  56. bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
  57. }
  58. /**
  59. * 解绑服务
  60. */
  61. public void unbindService(View view){
  62. try {
  63. if(mRemoteMusicManager != null && mRemoteMusicManager.asBinder().isBinderAlive()){
  64. mRemoteMusicManager.unRegistListener(musicArrivedListener);
  65. }
  66. } catch (RemoteException e) {
  67. e.printStackTrace();
  68. }
  69. unbindService(mConnection);
  70. }

这里须要说明的是在客户端进程中,经过DeathRecipient对象,调用IBinder的linkToDeath和unLinkToDeath方法,实现了 万一Server进程Died以后,对Service进行再次注册

简单介绍一下linkToDeath和unlinkToDeath:

  1. /**
  2. * Interface for receiving a callback when the process hosting an IBinder
  3. * has gone away.
  4. *
  5. * @see #linkToDeath
  6. */
  7. public interface DeathRecipient {
  8. public void binderDied();
  9. }
  10. /**
  11. * Register the recipient for a notification if this binder
  12. * goes away. If this binder object unexpectedly goes away
  13. * (typically because its hosting process has been killed),
  14. * then the given {@link DeathRecipient}'s
  15. * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
  16. * will be called.
  17. *
  18. * <p>You will only receive death notifications for remote binders,
  19. * as local binders by definition can't die without you dying as well.
  20. *
  21. * @throws RemoteException if the target IBinder's
  22. * process has already died.
  23. *
  24. * @see #unlinkToDeath
  25. */
  26. public void linkToDeath(@NonNull DeathRecipient recipient, int flags)
  27. throws RemoteException;
  28. /**
  29. * Remove a previously registered death notification.
  30. * The recipient will no longer be called if this object
  31. * dies.
  32. *
  33. * @return {@code true} if the <var>recipient</var> is successfully
  34. * unlinked, assuring you that its
  35. * {@link DeathRecipient#binderDied DeathRecipient.binderDied()} method
  36. * will not be called; {@code false} if the target IBinder has already
  37. * died, meaning the method has been (or soon will be) called.
  38. *
  39. * @throws java.util.NoSuchElementException if the given
  40. * <var>recipient</var> has not been registered with the IBinder, and
  41. * the IBinder is still alive. Note that if the <var>recipient</var>
  42. * was never registered, but the IBinder has already died, then this
  43. * exception will <em>not</em> be thrown, and you will receive a false
  44. * return value instead.
  45. */
  46. public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags);

使用它比较简单,只须要实现DeathRecipient的bindDied方法。

linkToDeath和unLinkToDeath是成对出现的,参照上面客户端中MainActivity的实现,在ServiceConnection中onServiceConnected的时候link,在bindDied方法中unlink。linkToDeath是为IBinder对象设置死亡代理,unLinkToDeath是解除以前设置的死亡代理,并能够在此时做从新绑定的动作。

注意

  1. AIDL只提供了有限的传输数据类型,自定义的传输类型须要序列化
  2. RemoteCallbackList很好的解决了IPC过程当中可能出现的某一方进程Crash,引发另外一方Exception的问题
  3. DeathRecipient理解和调用(解决Server端进程意外终止,Client端得到的Binder对象丢失问题)

为什么这么麻烦,不用remoteCallList可以吗?

如果不用remoteCallList绑定和解绑listener会怎样,为什么这么麻烦呢?

可以增加几条打印,看看解绑和绑定时能不能成功:

 原因就在于我们的注册对象listener是在进程间传输的,Binder在服务端会把客户端传递过来的对象重新转换为新的对象,因而注册和解注册的根本就不是一个对象,当然不能达到解除注册的目的了

我们在服务端解除listener的代码处添加下面两行代码:

  1. System.out.println("解绑定时候的listener:  "+listener);
  2. System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());

在服务端绑定listener的代码处下面两行代码:

  1. System.out.println("绑定时候的listener:  "+listener);
  2. System.out.println("绑定时候的listener对应的Binder:  "+listener.asBinder());

 而后运行,查看Log输出:

 从Log输出上面很明显的可以看到绑定和解绑定的listener并不是同一个对象,那么解除绑定肯定是失败的,但是从Log输出上可以看出listener所对应的Binder对象是相同的,他们都是BinderProxy对象,原因在于:跨进程间通信时,服务端要创建Binder对象,客户端要拿到服务端Binder对象在Binder驱动中的引用,对应到客户端就是BinderProxy对象了,我们这里的情况是服务端和客户端通信,那么此时的服务端将成为客户端,所以收到的Binder就是BinderProxy类型了,为什么绑定和解绑定过程中listener对象不同,但是listener对象对应的Binder对象却的相同的呢?这个我们需要看下系统为我们的IMessageManager.aidl生成的.java文件内容了:

  1. @Override
  2. public void registerLoginUser(com.hzw.messagesend.ILoginOnListener listener) throws android.os.RemoteException
  3. {
  4. android.os.Parcel _data = android.os.Parcel.obtain();
  5. android.os.Parcel _reply = android.os.Parcel.obtain();
  6. try {
  7. _data.writeInterfaceToken(DESCRIPTOR);
  8. _data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));
  9. mRemote.transact(Stub.TRANSACTION_registerLoginUser, _data, _reply, 0);
  10. _reply.readException();
  11. }
  12. finally {
  13. _reply.recycle();
  14. _data.recycle();
  15. }
  16. }

其他代码就不展示了。因此,如果不用RemoteCallList而用普通的Arralist的话,也可以

  1. public void unRegisterListener(INewMusicArrivedListener listener)
  2. throws RemoteException {
  3. for(int i = list.size()-1; i >= 0; i--)
  4. {
  5. if(list.get(i).asBinder() == listener.asBinder())
  6. {
  7. //解除绑定注册
  8. System.out.println("解绑定时候的listener: "+listener);
  9. System.out.println("解绑定时候的listener对应的Binder: "+listener.asBinder());
  10. list.remove(list.get(i));
  11. break;
  12. }
  13. }
  14. }
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/我家小花儿/article/detail/122856
推荐阅读
相关标签
  

闽ICP备14008679号