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

来源:互联网 发布:网络增值业务许可证 编辑:程序博客网 时间:2024/06/05 16:56

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

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

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

public class NewMessage implements Parcelable {public String senderID;public String messageContent;public String receiverID;@Overridepublic int describeContents() {return 0;}@Overridepublic void writeToParcel(Parcel out, int flags) {out.writeString(senderID);out.writeString(messageContent);out.writeString(receiverID);}public static final Parcelable.Creator<NewMessage> CREATOR = new Creator<NewMessage>() {@Overridepublic NewMessage[] newArray(int size) {return new NewMessage[size];}@Overridepublic NewMessage createFromParcel(Parcel in) {return new NewMessage(in);}};private NewMessage(Parcel in){senderID = in.readString();messageContent = in.readString();receiverID = in.readString();}public NewMessage(String senderID,String messageContent,String receiverID){this.senderID = senderID;this.messageContent = messageContent;this.receiverID = receiverID;}}
        接着定义了消息本身的aidl接口

package com.hzw.messagesend;parcelable NewMessage;
        定义了用于向服务端注册和解注册的接口:

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

        ILoginOnListener.aidl

package com.hzw.messagesend;import com.hzw.messagesend.NewMessage;interface ILoginOnListener{void OnNewMessageArrived(in NewMessage newMessage);}
        服务端:

public class MessageSendService extends Service{//用于表示service是否存活着public AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);//表示所有注册的用户public CopyOnWriteArrayList<ILoginOnListener> list = new CopyOnWriteArrayList<ILoginOnListener>();//public RemoteCallbackList<ILoginOnListener> list = new RemoteCallbackList<ILoginOnListener>();public IBinder binder = new IMessageManager.Stub() {@Overridepublic void unRegisterLoginUser(ILoginOnListener listener)throws RemoteException {if(list.contains(listener))list.remove(listener);elseSystem.out.println("没找到用户,解除绑定失败......");}@Overridepublic void registerLoginUser(ILoginOnListener listener)throws RemoteException {if(!list.contains(listener))list.add(listener);elseSystem.out.println("此用户已经正在监听......");}};@Overridepublic void onCreate() {super.onCreate();new Thread(new MessageRunnable()).start();}@Overridepublic void onDestroy() {mIsServiceDestroyed.set(true);//设置取消服务super.onDestroy();}//发送心跳包的线程,用于每隔1秒查看是否有用户给当前用户发送消息private class MessageRunnable implements Runnable{public void run() {while(!mIsServiceDestroyed.get()){//这里仅仅模拟了两个用户int senderID = (int)(Math.random()*10);int receiverID = (int)(Math.random()*2);if(senderID == receiverID)continue;//自己不能给自己发消息try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}String messageContent = getNowTime()+"** "+senderID+"向"+receiverID+"发送了一条消息";//service存活的话NewMessage message = new NewMessage(senderID+"", messageContent, receiverID+"");onNewMesssageArrived(message);System.out.println(messageContent);}}};//将消息分发给所有已经注册的用户public void onNewMesssageArrived(NewMessage message){for(int i = 0;i < list.size();i++){ILoginOnListener listener = list.get(i);try {if(listener != null)listener.OnNewMessageArrived(message);} catch (RemoteException e) {e.printStackTrace();}}}public String getNowTime(){//将当前时间转换为HH:mm:ss的形式return new SimpleDateFormat("(HH:mm:ss)").format(new Date(System.currentTimeMillis()));}@Overridepublic IBinder onBind(Intent arg0) {return binder;}}
        服务端代码也比较简单,这里模拟了有10个用户每隔500ms给两个用户来发送消息,同时在注册和解注册方法中分别打印了Log;

        客户端:

public class MessageSendActivity extends Activity implements OnClickListener{public TextView mTextView;public Button mStartReceive;public Button mStopReceive;public static final int NEW_MESSAGE = 1;public IMessageManager mManager;public boolean isRegister = false;//表示客户端是否注册监听到服务端public boolean isRepeat = false;//表示是否是重新开启的service连接public Handler mHandler = new Handler(){public void handleMessage(android.os.Message msg) {switch (msg.what) {case NEW_MESSAGE:NewMessage message = (NewMessage) msg.obj;mTextView.setText(mTextView.getText()+"\n"+message.messageContent);break;default:break;}};};public ILoginOnListener listener = new ILoginOnListener.Stub(){@Overridepublic void OnNewMessageArrived(NewMessage newMessage)throws RemoteException {if(newMessage.receiverID.equals("1")){//表示是发给自己的消息,需要显示在界面上mHandler.obtainMessage(NEW_MESSAGE,newMessage).sendToTarget();}}};public DeathRecipient mDeathRecipient = new DeathRecipient() {@Overridepublic void binderDied() {System.out.println("binderDied: "+Thread.currentThread().getName());if(mManager == null)return;//表示服务端返回的binder是null//解除bindermManager.asBinder().unlinkToDeath(mDeathRecipient,0);mManager = null;//重新与服务端建立连接Intent intent = new Intent(MessageSendActivity.this, MessageSendService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);isRepeat = true;}};public ServiceConnection connection = new ServiceConnection() {@Overridepublic void onServiceDisconnected(ComponentName arg0) {mManager = null;//表示客户端与服务之间的连接断开了}@Overridepublic void onServiceConnected(ComponentName arg0, IBinder service) {IMessageManager manager = IMessageManager.Stub.asInterface(service);try {service.linkToDeath(mDeathRecipient, 0);} catch (RemoteException e1) {e1.printStackTrace();}mManager = manager;if(isRepeat == true)System.out.println("Binder死亡之后重新建立的连接");elseSystem.out.println("Binder没有死亡");isRepeat = false;try {manager.registerLoginUser(listener);isRegister = true;//客户端注册监听到服务端} catch (RemoteException e) {e.printStackTrace();}}};@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);initView();}public void initView(){mTextView = (TextView)findViewById(R.id.textView);mStartReceive = (Button)findViewById(R.id.startReceive);mStopReceive = (Button)findViewById(R.id.stopReceive);mStartReceive.setOnClickListener(this);mStopReceive.setOnClickListener(this);}@Overridepublic void onClick(View view) {switch (view.getId()) {case R.id.startReceive://绑定服务Intent intent = new Intent(this, MessageSendService.class);bindService(intent, connection, Context.BIND_AUTO_CREATE);break;case R.id.stopReceive:if(isRegister){//判断如果已经注册过的话,则将其解注册if(mManager != null && mManager.asBinder().isBinderAlive()){try {mManager.unRegisterLoginUser(listener);} catch (RemoteException e) {e.printStackTrace();}}isRegister = false;}break;default:break;}}@Overrideprotected void onDestroy() {if(isRegister){//判断如果Binder服务端没有断开的话,则解除客户端消息监听绑定if(mManager != null && mManager.asBinder().isBinderAlive()){try {mManager.unRegisterLoginUser(listener);} catch (RemoteException e) {e.printStackTrace();}}}//解除客户端绑定unbindService(connection);super.onDestroy();}}

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

        查看下输出效果:

                                                                  

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

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

                                           

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

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

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

System.out.println("解绑定时候的listener:  "+listener);System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());
        在服务端21行之后添加下面两行代码:

System.out.println("绑定时候的listener:  "+listener);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文件内容了:

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

@Override public void registerLoginUser(com.hzw.messagesend.ILoginOnListener listener) throws android.os.RemoteException{android.os.Parcel _data = android.os.Parcel.obtain();android.os.Parcel _reply = android.os.Parcel.obtain();try {_data.writeInterfaceToken(DESCRIPTOR);_data.writeStrongBinder((((listener!=null))?(listener.asBinder()):(null)));mRemote.transact(Stub.TRANSACTION_registerLoginUser, _data, _reply, 0);_reply.readException();}finally {_reply.recycle();_data.recycle();}}

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

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

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

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

public void unRegisterLoginUser(ILoginOnListener listener)throws RemoteException {for(int i = 0;i < list.size();i++){if(list.get(i).asBinder() == listener.asBinder()){//解除绑定注册System.out.println("解绑定时候的listener:  "+listener);System.out.println("解绑定时候的listener对应的Binder:  "+listener.asBinder());list.remove(list.get(i));break;}}}

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

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

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


    

                                         



                                                                 

0 0