Android AIDL通信之注册和解注册监听器

使用场景

在AIDL通信时,通常我们可以利用观察者模式将客户端注册到服务端,接着有消息的时候服务端相应的通知各个客户端就可以了,这种方式在客户端和服务端处于同一进程的时候使用是没有问题的,因为同一进程内部是可以直接传递对象的,并不会出现注册绑定到服务端和解注册的对象不同的情况。但是如果放到不同进程间的话,因为通信过程中涉及到了序列化反序列化过程,那么势必会造成你注册的对象和解注册的对象并不是同一对象的情况。这时使用普通的解注册方法就无效了。下面列出了我的使用场景。

普通方式注册和解注册

监听器AIDL接口

1
2
3
4
5
6
7
8
9
// IATServiceListener.aidl
package com.flyscale.atclient.service;

import com.flyscale.atclient.bean.SubPhone;

interface IATServiceListener {
    void onSubPhoneCallStateChanged(String id, int status);
    void onRefreshRegisteredSubPhone(out List<SubPhone> subPhones);
}

注册和解注册监听器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//保存监听器的列表
private CopyOnWriteArrayList<IATServiceListener> mATServieListeners = new CopyOnWriteArrayList<IATServiceListener>();

//注册
public void addATServiceListener(IATServiceListener listener) {
    mATServieListeners.add(listener);
}

//解注册
public void removeATServiceListener(IATServiceListener listener) {
    Log.i(TAG, "removeATServiceListener");
    if(mATServieListeners.contains(listener)){
        Log.d(TAG, "remove listener success!");
        mATServieListeners.remove(listener);
    }
}

测试情况:
在跨进程情况下,调用注册和解注册接口,最终是没有打印Log.d(TAG, "remove listener success!");这一行的。这是为什么呢?
原因就在于我们的注册对象listener是在进程间传输的,Binder在服务端会把客户端传递过来的对象重新转换为新的对象,因而注册和解注册的根本就不是一个对象。它们在内存中的地址是不同的。

Binder的跨进程机制

AIDL其实就是使用的Binde机制来跨进程通信,所以它肯定有办法把两个不同进程中的两个对象联系起来。其实在注册和解注册时加上如下log就会发现:

1
Log.d(TAG, listener.asBinder());

两个对象的Binder是一样的,这样就好办了。我们通过直接判断它们的Binder是否相同就可以了。

1
2
3
4
5
6
7
8
9
10
public void removeATServiceListener(IATServiceListener listener) {
    Log.i(TAG, "removeATServiceListener");
    for(IATServiceListener:localListener: mATServieListeners){
        if(localListener.asBinder == listener.asBinder()){
            Log.d(TAG, "remove listener success!");
            mATServieListeners.remove(listener);
        }
    }  
   
}

问题解决了!