aidl的学习记录3

aidl相关文章

首先放出前两篇关于aidl的文章,没看过的可以看下。

aidl的学习记录

aidl的学习记录2

前言

要实现服务端每增加一本书,会主动通知客户端。不用客户端一直去调用getBooks获取最新书,采用观察者模式。当服务端有新书到来时,会通知每一个已经注册监听的客户端,并把新书对象传给客户端。

具体实现

1.由于AIDL无法使用普通接口所以需要新建一个aidl接口INewBookArrivedListener.adil,提供一个通知方法,注意导包。

package kanghb.com.aidltest;
import kanghb.com.aidltest.Book;

// Declare any non-default types here with import statements

interface INewBookArrivedListener {
    void onNewBookArrived(in Book newBook);
}

2.在AidlBookManager.aidl中加入两个方法

interface AidlBookManager {
    /**
     * Demonstrates some basic types that you can use as parameters
     * and return values in AIDL.
     */
  //所有的返回值前都不需要加任何东西,不管是什么数据类型
    List<Book> getBooks();

    //传参时除了Java基本类型以及String,CharSequence之外的类型
    //都需要在前面加上定向tag,具体加什么量需而定
    void addBook(in Book book);
    //新加两个方法
    void registerListener(INewBookArrivedListener listener);
    void unRegisterListener(INewBookArrivedListener listener);
}

3.点击makeProject(ctr+F9) 在build/generated/source/aidl/你的 flavor/重新生成 下生成一下 Java 文件AidlBookManager.java,接下来AIDLService里必然会报错,因为在AidlBookManager新加了方法。

4.实现服务端代码,AIDLService里实现新加的两个方法。同时,让服务端没隔5秒新加一本书。

/**
 CopyOnWriteArrayList支持并发读写,AIDL方法是在服务端的Binder线程池中执行的,因此当多个客户端同时连接的时候,会存在多个线程同时访问的情形,所以我们要在AIDL方法中处理线程同步,这里使用CopyOnWriteArrayList来进行自动的线程同步。因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中 会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端,所以采用CopyOnWriteArrayList是可以的,类似的还有ConcurrentHashMap。
 */   
private CopyOnWriteArrayList<INewBookArrivedListener> iNewBookArrivedListeners = new CopyOnWriteArrayList<>();
private AtomicBoolean isServiceDestroyed = new AtomicBoolean(false);
   private final AidlBookManager.Stub bookManager = new AidlBookManager.Stub() {
     //  。。。省略getBooks和addBook(Book book)那两个方法,主要看这两个方法
@Override
        public void registerListener(INewBookArrivedListener listener) throws RemoteException {
            if(!iNewBookArrivedListeners.contains(listener)){
                iNewBookArrivedListeners.add(listener);
            }else {
                Log.e(TAG,"already exists.");
            }
            Log.e(TAG,"registerListener,size:" + iNewBookArrivedListeners.size());
        }

        @Override
        public void unRegisterListener(INewBookArrivedListener listener) throws RemoteException {
            if(iNewBookArrivedListeners.contains(listener)){
                iNewBookArrivedListeners.remove(listener);
                Log.e(TAG,"unregister listener succeed");
            }else {
                Log.e(TAG,"not found,unregister faild");
            }
            Log.e(TAG,"unregisterListener,size:" + iNewBookArrivedListeners.size());
        }
   }
  @Override
    public void onCreate() {
        super.onCreate();
        Book book = new Book();
        book.setName("Android开发艺术探索");
        book.setPrice(28);
        books.add(book);
        //没隔5秒新加一本书
        new Thread(new ServiceRunnable()).start();
    }

    private class ServiceRunnable implements Runnable{

        @Override
        public void run() {
            while (!isServiceDestroyed.get()){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Book book = new Book();
                book.setName("服务端加新书啦" + books.size());
                book.setPrice(books.size());
                books.add(book);
                for (int i = 0; i < iNewBookArrivedListeners.size(); i++) {
                    INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.get(i);
                    try {
                        iNewBookArrivedListener.onNewBookArrived(book);
                    } catch (RemoteException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    @Override
    public void onDestroy() {
        super.onDestroy();
        isServiceDestroyed.set(true);
    }

5.实现客户端代码(注意将aidl文件拷贝到客户端,因为这里客户端和服务端是两个app)

private INewBookArrivedListener iNewBookArrivedListener = new INewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            //在客户端的Binder线程池中执行,所以要用handler切换到ui线程.
            mHander.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
        }
    };
 public static Handler mHander = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Log.e(TAG, "receive new book:" + msg.obj);
                    break;
                default:
                    super.handleMessage(msg);

            }
        }
    };

在onServiceConnected里注册和onDestroy中反注册

    //注册INewBookArrivedListener到服务端
   aidlBookManager.registerListener(iNewBookArrivedListener);
    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (aidlBookManager != null && aidlBookManager.asBinder().isBinderAlive()) {
            try {
                //反注册
                aidlBookManager.unRegisterListener(iNewBookArrivedListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        if (mBound) {
            unbindService(serviceConnection);
            mBound = false;
        }
    }

启动客户端和服务端运行看效果如下,客户端确实可以收到通知了。

但是,当客户端点击back结束mainActivity时,并没有成功取消注册。这是因为什么呢?

)

RemoteCallbackList

介绍

上述问题是因为在多进程中Binder会把传过来的对象重新转化为另外一个对象,虽然在客户端注册和反注册都使用同一个iNewBookArrivedListener对象,但是Binder传送到服务端后会产生一个新的对象。RemoteCallbackList是专门用来取消跨进程接口的,RemoteCallbackList是一个泛型,支持任意AIDL接口。

public class RemoteCallbackList<E extends IInterface>
ArrayMap<IBinder, Callback> mCallbacks
            = new ArrayMap<IBinder, Callback>();

内部有一个Map结构用来保存AIDL回调,Map的key是IBinder类型,value是Callback类型。当客户端注册iNewBookArrivedListener时,会把每个iNewBookArrivedListener的信息都存入mCallbacks中。

用法

1.替换iNewBookArrivedListeners的类型CopyOnWriteArrayList改为RemoteCallbackList

    private RemoteCallbackList<INewBookArrivedListener> iNewBookArrivedListeners = new RemoteCallbackList<>();
//    private CopyOnWriteArrayList<INewBookArrivedListener> iNewBookArrivedListeners = new CopyOnWriteArrayList<>();

2.修改registerListener和unRegisterListener方法的实现,变得很简单了。打印log时获取数量的方法也变了

   @Override
        public void registerListener(INewBookArrivedListener listener) throws RemoteException {
            iNewBookArrivedListeners.register(listener);
//            if(!iNewBookArrivedListeners.contains(listener)){
//                iNewBookArrivedListeners.add(listener);
//            }else {
//                Log.e(TAG,"already exists.");
//            }
            Log.e(TAG,"registerListener,size:" + iNewBookArrivedListeners.getRegisteredCallbackCount());
        }

        @Override
        public void unRegisterListener(INewBookArrivedListener listener) throws RemoteException {
            iNewBookArrivedListeners.unregister(listener);
//            if(iNewBookArrivedListeners.contains(listener)){
//                iNewBookArrivedListeners.remove(listener);
//                Log.e(TAG,"unregister listener succeed");
//            }else {
//                Log.e(TAG,"not found,unregister faild");
//            }
            Log.e(TAG,"unregisterListener,size:" + iNewBookArrivedListeners.getRegisteredCallbackCount());
        }

3.更改通知客户端的方法,注意遍历RemoteCallBackListbeginBroadcastfinishBroadcast方法必须配对使用。

//                for (int i = 0; i < iNewBookArrivedListeners.size(); i++) {
//                    INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.get(i);
//                    try {
//                        iNewBookArrivedListener.onNewBookArrived(book);
//                    } catch (RemoteException e) {
//                        e.printStackTrace();
//                    }
//                }
                int n = iNewBookArrivedListeners.beginBroadcast();
                for (int i = 0; i < n; i++) {
                    INewBookArrivedListener iNewBookArrivedListener = iNewBookArrivedListeners.getBroadcastItem(i);
                    if(iNewBookArrivedListener != null){
                        try {
                            iNewBookArrivedListener.onNewBookArrived(book);
                        } catch (RemoteException e) {

                        }
                    }

                }
                iNewBookArrivedListeners.finishBroadcast();

然后运行服务端app,客户端重新连接点击发送后,退出mainactivity看log,可以看出使用了RemoteCallBackList后,的确可以完成跨进程的反注册功能。

来点干货

aidl的学习记录2中我们讲过给BInder设置DeathRecipient监听可以收到binderDied()回调,然后重连远程服务。这样可以避免Binder意外死亡给客户端造成的影响。我们知道还有一种方法是在onServiceDisconnected方法中重连远程服务。具体使用哪种可以二选一,那么这两者之间的区别是什么?

使用方法onServiceDisconnected()binderDied()
运行线程客户端UI线程客户端Binder线程池

代码传送门:github

------ 本文结束 ------
坚持原创技术分享,您的支持将鼓励我继续创作!