android-----关于通过AIDL注册监听之后无法解除监听的探索

来源:互联网 发布:linux安装文件格式 编辑:程序博客网 时间:2024/06/05 22:54
        我们在平常使用AIDL时可能会有这样的场景,客户端并不想一直查看服务端有没有有关我的消息,而是想让服务端在有消息的时候能够通知我,随后客户端再去服务端拿消息,这样相对来说比较节省资源,通常我们可以利用观察者模式将客户端注册到服务端,接着有消息的时候服务端相应的通知各个客户端就可以了,这种方式在客户端和服务端处于同一进程的时候使用是没有问题的,因为同一进程内部是可以直接传递对象的,并不会出现注册绑定到服务端和解注册的对象不同的情况,但是如果放到不同进程间的话,因为通信过程中涉及到了序列化反序列化过程,那么势必会造成你注册的对象和解注册的对象并不是同一对象的情况,这就是我这篇博客想要讨论的一个问题;

        首先我们来还原这个问题:

        首先,我创建了NewMessage类用来表示进程间传递的消息,实现了Parcelable序列化接口:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class NewMessage implements Parcelable {  
  2.   
  3.     public String senderID;  
  4.     public String messageContent;  
  5.     public String receiverID;  
  6.       
  7.     @Override  
  8.     public int describeContents() {  
  9.         return 0;  
  10.     }  
  11.   
  12.     @Override  
  13.     public void writeToParcel(Parcel out, int flags) {  
  14.         out.writeString(senderID);  
  15.         out.writeString(messageContent);  
  16.         out.writeString(receiverID);  
  17.     }  
  18.       
  19.     public static final Parcelable.Creator<NewMessage> CREATOR = new Creator<NewMessage>() {  
  20.           
  21.         @Override  
  22.         public NewMessage[] newArray(int size) {  
  23.             return new NewMessage[size];  
  24.         }  
  25.           
  26.         @Override  
  27.         public NewMessage createFromParcel(Parcel in) {  
  28.             return new NewMessage(in);  
  29.         }  
  30.     };  
  31.       
  32.     private NewMessage(Parcel in)  
  33.     {  
  34.         senderID = in.readString();  
  35.         messageContent = in.readString();  
  36.         receiverID = in.readString();  
  37.     }  
  38.       
  39.     public NewMessage(String senderID,String messageContent,String receiverID)  
  40.     {  
  41.         this.senderID = senderID;  
  42.         this.messageContent = messageContent;  
  43.         this.receiverID = receiverID;  
  44.     }  
  45. }  
        接着定义了消息本身的aidl接口

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.hzw.messagesend;  
  2.   
  3. parcelable NewMessage;  
        定义了用于向服务端注册和解注册的接口:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.hzw.messagesend;  
  2.   
  3. import com.hzw.messagesend.NewMessage;  
  4. import com.hzw.messagesend.ILoginOnListener;  
  5.  interface IMessageManager  
  6.  {  
  7.     void registerLoginUser(ILoginOnListener listener);  
  8.     void unRegisterLoginUser(ILoginOnListener listener);  
  9.  }  
        因为在IMessageManager中用到了ILoginOnListener,这个接口里面有注册的方法,所以我们还需要定义ILoginOnListener的aidl接口,原因在于AIDL中是无法使用普通接口的,这个ILoginOnListener存在的目的是:我们的服务端要回传消息给客户端,此时原先的服务端将成为客户端,原先的客户端将成为服务端,两者之间同样是跨进程通信,那么我们就必须让ILoginOnListener成为能在Binder中传输的对象了,也就只好将其也定义成aidl接口了;

        ILoginOnListener.aidl

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. package com.hzw.messagesend;  
  2.   
  3. import com.hzw.messagesend.NewMessage;  
  4.   
  5. interface ILoginOnListener  
  6. {  
  7.     void OnNewMessageArrived(in NewMessage newMessage);  
  8. }  
        服务端:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MessageSendService extends Service{  
  2.   
  3.     //用于表示service是否存活着  
  4.     public AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);  
  5.     //表示所有注册的用户  
  6.     public CopyOnWriteArrayList<ILoginOnListener> list = new CopyOnWriteArrayList<ILoginOnListener>();  
  7.     //public RemoteCallbackList<ILoginOnListener> list = new RemoteCallbackList<ILoginOnListener>();  
  8.     public IBinder binder = new IMessageManager.Stub() {  
  9.           
  10.         @Override  
  11.         public void unRegisterLoginUser(ILoginOnListener listener)  
  12.                 throws RemoteException {  
  13.             if(list.contains(listener))  
  14.                 list.remove(listener);  
  15.             else  
  16.                 System.out.println("没找到用户,解除绑定失败......");  
  17.         }  
  18.           
  19.         @Override  
  20.         public void registerLoginUser(ILoginOnListener listener)  
  21.                 throws RemoteException {  
  22.             if(!list.contains(listener))  
  23.                 list.add(listener);  
  24.             else  
  25.                 System.out.println("此用户已经正在监听......");  
  26.         }  
  27.     };  
  28.       
  29.       
  30.     @Override  
  31.     public void onCreate() {  
  32.         super.onCreate();  
  33.         new Thread(new MessageRunnable()).start();  
  34.     }  
  35.     @Override  
  36.     public void onDestroy() {  
  37.         mIsServiceDestroyed.set(true);//设置取消服务  
  38.         super.onDestroy();  
  39.     }  
  40.     //发送心跳包的线程,用于每隔1秒查看是否有用户给当前用户发送消息  
  41.     private class MessageRunnable implements Runnable{  
  42.         public void run() {  
  43.             while(!mIsServiceDestroyed.get())  
  44.             {  
  45.                 //这里仅仅模拟了两个用户  
  46.                 int senderID = (int)(Math.random()*10);  
  47.                 int receiverID = (int)(Math.random()*2);  
  48.                 if(senderID == receiverID)  
  49.                     continue;//自己不能给自己发消息  
  50.                 try {  
  51.                     Thread.sleep(500);  
  52.                 } catch (InterruptedException e) {  
  53.                     e.printStackTrace();  
  54.                 }  
  55.                 String messageContent = getNowTime()+"** "+senderID+"向"+receiverID+"发送了一条消息";  
  56.                 //service存活的话  
  57.                 NewMessage message = new NewMessage(senderID+"", messageContent, receiverID+"");  
  58.                 onNewMesssageArrived(message);  
  59.                 System.out.println(messageContent);  
  60.             }  
  61.         }  
  62.     };  
  63.       
  64.     //将消息分发给所有已经注册的用户  
  65.     public void onNewMesssageArrived(NewMessage message)  
  66.     {  
  67.         for(int i = 0;i < list.size();i++)  
  68.         {  
  69.             ILoginOnListener listener = list.get(i);  
  70.             try {  
  71.                 if(listener != null)  
  72.                     listener.OnNewMessageArrived(message);  
  73.             } catch (RemoteException e) {  
  74.                 e.printStackTrace();  
  75.             }  
  76.         }  
  77.     }  
  78.       
  79.     public String getNowTime()  
  80.     {  
  81.         //将当前时间转换为HH:mm:ss的形式  
  82.         return new SimpleDateFormat("(HH:mm:ss)").format(new Date(System.currentTimeMillis()));  
  83.     }  
  84.     @Override  
  85.     public IBinder onBind(Intent arg0) {  
  86.         return binder;  
  87.     }  
  88. }  
        服务端代码也比较简单,这里模拟了有10个用户每隔500ms给两个用户来发送消息,同时在注册和解注册方法中分别打印了Log;

        客户端:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class MessageSendActivity extends Activity implements OnClickListener{  
  2.   
  3.     public TextView mTextView;  
  4.     public Button mStartReceive;  
  5.     public Button mStopReceive;  
  6.     public static final int NEW_MESSAGE = 1;  
  7.     public IMessageManager mManager;  
  8.     public boolean isRegister = false;//表示客户端是否注册监听到服务端  
  9.     public boolean isRepeat = false;//表示是否是重新开启的service连接  
  10.       
  11.     public Handler mHandler = new Handler(){  
  12.         public void handleMessage(android.os.Message msg) {  
  13.             switch (msg.what) {  
  14.             case NEW_MESSAGE:  
  15.                 NewMessage message = (NewMessage) msg.obj;  
  16.                 mTextView.setText(mTextView.getText()+"\n"+message.messageContent);  
  17.                 break;  
  18.             default:  
  19.                 break;  
  20.             }  
  21.         };  
  22.     };  
  23.       
  24.     public ILoginOnListener listener = new ILoginOnListener.Stub(){  
  25.         @Override  
  26.         public void OnNewMessageArrived(NewMessage newMessage)  
  27.                 throws RemoteException {  
  28.             if(newMessage.receiverID.equals("1"))  
  29.             {  
  30.                 //表示是发给自己的消息,需要显示在界面上  
  31.                 mHandler.obtainMessage(NEW_MESSAGE,newMessage).sendToTarget();  
  32.             }  
  33.         }  
  34.     };  
  35.       
  36.     public DeathRecipient mDeathRecipient = new DeathRecipient() {  
  37.           
  38.         @Override  
  39.         public void binderDied() {  
  40.             System.out.println("binderDied: "+Thread.currentThread().getName());  
  41.             if(mManager == null)  
  42.                 return;//表示服务端返回的binder是null  
  43.             //解除binder  
  44.             mManager.asBinder().unlinkToDeath(mDeathRecipient,0);  
  45.             mManager = null;  
  46.             //重新与服务端建立连接  
  47.             Intent intent = new Intent(MessageSendActivity.this, MessageSendService.class);  
  48.             bindService(intent, connection, Context.BIND_AUTO_CREATE);  
  49.             isRepeat = true;  
  50.         }  
  51.     };  
  52.       
  53.     public ServiceConnection connection = new ServiceConnection() {  
  54.           
  55.         @Override  
  56.         public void onServiceDisconnected(ComponentName arg0) {  
  57.             mManager = null;//表示客户端与服务之间的连接断开了  
  58.         }  
  59.           
  60.         @Override  
  61.         public void onServiceConnected(ComponentName arg0, IBinder service) {  
  62.             IMessageManager manager = IMessageManager.Stub.asInterface(service);  
  63.             try {  
  64.                 service.linkToDeath(mDeathRecipient, 0);  
  65.             } catch (RemoteException e1) {  
  66.                 e1.printStackTrace();  
  67.             }  
  68.             mManager = manager;  
  69.             if(isRepeat == true)  
  70.                 System.out.println("Binder死亡之后重新建立的连接");  
  71.             else  
  72.                 System.out.println("Binder没有死亡");  
  73.             isRepeat = false;  
  74.             try {  
  75.                 manager.registerLoginUser(listener);  
  76.                 isRegister = true;//客户端注册监听到服务端  
  77.             } catch (RemoteException e) {  
  78.                 e.printStackTrace();  
  79.             }  
  80.         }  
  81.     };  
  82.     @Override  
  83.     protected void onCreate(Bundle savedInstanceState) {  
  84.         super.onCreate(savedInstanceState);  
  85.         setContentView(R.layout.activity_main);  
  86.         initView();  
  87.     }  
  88.       
  89.     public void initView()  
  90.     {  
  91.         mTextView = (TextView)findViewById(R.id.textView);  
  92.         mStartReceive = (Button)findViewById(R.id.startReceive);  
  93.         mStopReceive = (Button)findViewById(R.id.stopReceive);  
  94.         mStartReceive.setOnClickListener(this);  
  95.         mStopReceive.setOnClickListener(this);  
  96.     }  
  97.   
  98.     @Override  
  99.     public void onClick(View view) {  
  100.         switch (view.getId()) {  
  101.         case R.id.startReceive:  
  102.             //绑定服务  
  103.             Intent intent = new Intent(this, MessageSendService.class);  
  104.             bindService(intent, connection, Context.BIND_AUTO_CREATE);  
  105.             break;  
  106.         case R.id.stopReceive:  
  107.             if(isRegister)  
  108.             {  
  109.                 //判断如果已经注册过的话,则将其解注册  
  110.                 if(mManager != null && mManager.asBinder().isBinderAlive())  
  111.                 {  
  112.                     try {  
  113.                         mManager.unRegisterLoginUser(listener);  
  114.                     } catch (RemoteException e) {  
  115.                         e.printStackTrace();  
  116.                     }  
  117.                 }  
  118.                 isRegister = false;  
  119.             }  
  120.             break;  
  121.         default:  
  122.             break;  
  123.         }  
  124.     }  
  125.       
  126.     @Override  
  127.     protected void onDestroy() {  
  128.         if(isRegister)  
  129.         {  
  130.             //判断如果Binder服务端没有断开的话,则解除客户端消息监听绑定  
  131.             if(mManager != null && mManager.asBinder().isBinderAlive())  
  132.             {  
  133.                 try {  
  134.                     mManager.unRegisterLoginUser(listener);  
  135.                 } catch (RemoteException e) {  
  136.                     e.printStackTrace();  
  137.                 }  
  138.             }  
  139.         }  
  140.         //解除客户端绑定  
  141.         unbindService(connection);  
  142.         super.onDestroy();  
  143.     }  
  144. }  

        客户端模拟了当前用户ID是1,并且在收到消息之后判断接受ID是不是1,如果是的话会通过Handler发送到主线程上面,同时更新界面显示,这里为什么要用到Handler,原因在于OnNewMessageArrived方法是运行在客户端的Binder线程池中的,并不是主线程,不能进行更新UI的操作,为了防止服务端进程被杀死,我们在客户端设置了死亡代理,只要服务端进程被意外关掉,都会回调
binderDied方法,在这个方法里面我们做了重新绑定service操作,其他部分代码就不再详解了;

        查看下输出效果:

                                                                  

        我们在点开始接收消息的时候,每隔500ms就有别的客户端向2个客户端随机发消息,上面显示的是客户端1收到的消息过程;

但是当我们点击停止接收消息后,查看Log输出:

                                            

        可以看到在我们点击完按钮之后,消息并没有停止接收,而是输出解除绑定失败,这是怎么回事呢?

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

        我们在服务端代码第12行之后添加下面两行代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. System.out.println("解绑定时候的listener:  "+listener);  
  2.             System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());  
        在服务端21行之后添加下面两行代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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文件内容了:

        在他内部的代理类中,我们看到如下代码:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  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. }  

        在第8行,你会看到调用了writeStrongBinder方法,这个方法会将listener对应的Binder对象写进去,那么我们在调用服务端的onTransact方法的时候就会获取到这个Binder对象,我们可以来看看服务端的onTransact方法case语句为TRANSACTION_registerLoginUser的部分:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. case TRANSACTION_registerLoginUser:  
  2. {  
  3. data.enforceInterface(DESCRIPTOR);  
  4. com.hzw.messagesend.ILoginOnListener _arg0;  
  5. _arg0 = com.hzw.messagesend.ILoginOnListener.Stub.asInterface(data.readStrongBinder());  
  6. this.registerLoginUser(_arg0);  
  7. reply.writeNoException();  
  8. return true;  
  9. }  
        可以看到第5行调用了readStrongBinder方法,从输入数据中取出了Binder对象,接下来在调用unRegisterLoginUser的时候也会调用writeStrongBinder写入listener对应的Binder对象,调用readStrongBinder读出写入的Binder对象,而因为是进程间的通信,所以这里的Binder对象实际上是BinderProxy对象,对于同一进程而言,其获取到的BinderProxy对象是相同的,所以registerLoginUser和
unRegisterLoginUser写入的Binder是同一个Binder;

        我们可以利用registerLoginUser和unRegisterLoginUser写入的Binder一致来解决无法解除绑定注册这个问题:

        将服务端unRegisterLoginUser修改成下面这样:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public void unRegisterLoginUser(ILoginOnListener listener)  
  2.                 throws RemoteException {  
  3.             for(int i = 0;i < list.size();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.         }  

        随后运行程序,点击停止消息接收,可以发现界面上不再有消息刷新出现;

        上面解除绑定注册的方法比较暴力,就是直接循环看看我的所有注册的listener里面有没有listener对应的Binder等于要解除的listener的,有的话,直接将该listener从监听列表中删除就可以了;

        可以发现,上面的暴力式方式完全可以用Maap来实现,原因就在于每一个进程对应的Binder对象是唯一的,只要是在同一个进程中,不管你在哪获取Binder都是相同的,那么我们可以把Binder作为Map的key值,value值就可以是listener对象了,因为每一个进程对应一个用户嘛,这样相对来说效率比较高,这也就是android给我们提供的RemoteCallBack实现原理了,具体RemoteCallback怎么用,网上资料就比较多了,在此不再细说;
0 0
原创粉丝点击