使用AIDL的进阶知识点

提醒:这篇博文是基于上一篇《学习笔记之IPC》中的AIDL的,如果还不清楚AIDL的基础用法,建议先去阅读它的AIDL部分

在AIDL中使用观察者模式

在上篇博文中,我们实现了一个简单的图书馆(服务端),然后用户(客户端)可以查询所有书籍,并请求添加书籍。现在我们想为其应用观察者模式,让用户成为观察者,每当图书馆有新书到店时,用户就收到新书的消息。

1.首先我们应该定义客户端使用的监听器,有了监听器,我们才能在服务端对监听器(实则用户)进行管理。

在原有的AIDL包中,新建一个监听器接口的AIDL文件IOnNewBookArrivedListener.aidl

package ein.aidlserver;

import ein.aidlserver.Book;

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

然后编辑IBookManager.aidl文件,为其添加两个方法,分别用来注册监听器和注销监听器

package ein.aidlserver;

import ein.aidlserver.Book;
import ein.aidlserver.IOnNewBookArrivedListener;

interface IBookManager {
    List<Book> getBookList();
    void addBook(in Book book);
    void registerListener(IOnNewBookArrivedListener listener);
    void unregisterListener(IOnNewBookArrivedListener listener);
}

最后将更改后的整个AIDL文件包拷贝至客户端。

2.修改服务端的代码。主要是重写新增的两个方法:registerListener和unregisterListener。然后为了便于测试效果,开启一个子线程自动添加书籍,并在每次添加书籍之后回调监听器的onNewBookArrived方法。如下

public class BookManagerService extends Service {
    private static final String TAG = "BookManagerService";

    private AtomicBoolean mIsServiceDestroyed = new AtomicBoolean(false);
    private CopyOnWriteArrayList<Book> mBooklist = new CopyOnWriteArrayList<>();
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
    private Binder mBinder = new IBookManager.Stub() {
        @Override
        public List<Book> getBookList() throws RemoteException {
            return mBooklist;
        }

        @Override
        public void addBook(Book book) throws RemoteException {
            mBooklist.add(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.unregister(listener);
        }
    };

    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mBooklist.add(new Book("The Da Vinci Code", 1));
        mBooklist.add(new Book("Thinking in Java", 2));
        //开启子线程,执行自动添加书籍的任务
        new Thread(new ServiceWorker()).start();
    }

    @Override
    public void onDestroy() {
        mIsServiceDestroyed.set(true);
        super.onDestroy();
    }

    /**
     * 自动添加书籍的任务
     */
    private class ServiceWorker implements Runnable {

        @Override
        public void run() {
            //如果服务尚未被销毁
            while (!mIsServiceDestroyed.get()) {
                try {
                    //每五秒添加一本书
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //bookId从1开始计数
                int bookId = mBooklist.size() + 1;
                Book newBook = new Book("new book#" + bookId, bookId);
                try {
                    onNewBookArrived(newBook);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    private void onNewBookArrived(Book book) throws RemoteException {
        mBooklist.add(book);
        //通知所有订阅者
        final int num = mListenerList.beginBroadcast();
        for (int i = 0; i < num; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null) {
                listener.onNewBookArrived(book);
            }
        }
        mListenerList.finishBroadcast();
    }
}

其中有些细节需要注意一下:

  • 用来标识服务是否被销毁的的flag用的不是boolean而是AtomicBoolean,这是因为AIDL的方法是在Binder的线程池中执行的,在多用户连接的情况下,会存在多线程同时访问的情形,而使用AtomicBoolean可以支持并发读写。
  • 出于上述原因我们使用的mBookList是CopyOnWriteArrayList类型。但是在上一篇文章中曾说AIDL能使用的List不是只有ArrayList么?这是因为AIDL中所支持的是抽象的List,而List只是一个接口,因此虽然服务端返回的是CopyOnWriteArrayList,但是在Binder中会按照List的规范去访问数据并最终形成一个新的ArrayList传递给客户端。
  • 既然书籍列表用的CopyOnWriteArrayList,那么监听器列表为什么不也用CopyOnWriteArrayList呢?其根本原因是为了在注销监听器的时候能够注销掉一开始我们注册的那个。因为,在IPC中虽然我们在注册和注销监听器的时候传入的是同一个客户端对象,但是通过Binder传递到服务端后,却会产生两个全新的对象(因为服务器是根据反序列化拿到对象的)。而使用RemoteCallbackList的好处就是,RemoteCallbackList内部是一个ArrayMap,而Map的数据结构是键值对,其中key是IBinder类型。虽然在IPC中客户端传给服务端的同一个对象会生成不同的对象,但它们的内部Binder对象是同一个。
  • 服务端的onNewBookArrived方法中回调了监听器的onNewBookArrived方法。如果客户端的这个onNewBookArrived方法比较耗时,那么请确保服务端的onNewBookArrived运行在非UI线程中,否则将导致服务端无法响应。

3.修改客户端代码。首先要做的就是监听器的注册和注销工作,在这里我们分别在绑定服务的时候注册监听器,以及在销毁Activity的时候注销监听器;然后在监听器的回调方法onNewBookArrived中使用handler将线程切换到UI线程以便更新界面(这是因为onNewBookArrived方法是在客户端的Binder线程池中执行的)。 

public class MainActivity extends AppCompatActivity {

    private static final String TAG = "MainActivity";
    private static final int MESSAGE_NEW_BOOK_ARRIVED = 1;

    private IBookManager mRemoteBookManager;

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //在主线程中执行
            switch (msg.what){
                case MESSAGE_NEW_BOOK_ARRIVED:
                    Book book = (Book)msg.obj;
                    Log.d(TAG, "receive new book, book's name is "+book.getBookName());
                    break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private IOnNewBookArrivedListener mListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            //在Binder线程池中执行
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED,newBook).sendToTarget();
            //上面这行等效于下面三行
            //Message msg = Message.obtain(null,MESSAGE_NEW_BOOK_ARRIVED);
            //msg.obj = newBook;
            //mHandler.sendMessage(msg);
        }
    };

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                //由于目前方法体是执行在UI线程中的,假设getBookList方法耗时,需要再开子线程去执行
                List<Book> list = bookManager.getBookList();
                for(Book book : list)
                    Log.d(TAG, "query book list: "+"book id is "+book.getBookId()+" book name is "+book.getBookName());
                //添加书籍
                bookManager.addBook(new Book("In the Cases",3));
                list = bookManager.getBookList();
                for(Book book : list)
                    Log.d(TAG, "query book list: "+"book ishid is "+book.getBookId()+" book name is "+book.getBookName());

                //向服务器注册监听器
                bookManager.registerListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //绑定服务
        Intent intent = new Intent();
        intent.setAction("com.xxxx.xxxxx.aidldemoserver.BookManagerService");
        intent.setPackage("com.xxxx.xxxxx.aidldemoserver");
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }

    @Override
    protected void onDestroy() {
        if(mRemoteBookManager != null
                && mRemoteBookManager.asBinder().isBinderAlive()){
            Log.d(TAG, "unregister listener: "+mListener);
            try {
                mRemoteBookManager.unregisterListener(mListener);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        unbindService(connection);
        super.onDestroy();
    }
}

客户端比较简单,我就再提醒一下:客户端的onServiceConnected 和 onServiceDisconnected方法都是执行在UI线程中的,所以不可以在它们里面直接调用服务端的耗时方法,这点要切记。另一方面,由于服务端的方法本身就运行在服务端的Binder线程池中,所以服务端方法本身就可以执行大量耗时工作,这个时候不要在服务端方法中开线程去执行异步任务。

增强程序健壮性

为了增强程序的健壮性。当服务端进程意外停止导致Binder的意外死亡时,我们应该重新连接服务。

方法一:使用DeathRecipient。它是一个接口,当我们使用linkToDeath方法为Binder设置了该接口后,Binder死亡的时候,就会回调DeathRecipient的binderDied方法。

首先在客户端中声明一个DeathRecipient

private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
    @Override
    public void binderDied() {
        if(mRemoteBookManager == null)
            return;
        mRemoteBookManager.asBinder().unlinkToDeath(mDeathRecipient,0);
        mRemoteBookManager = null;
        //重新绑定远程服务
        Intent intent = new Intent();
        intent.setAction("com.xxxx.xxxxx.aidldemoserver.BookManagerService");
        intent.setPackage("com.xxxx.xxxxx.aidldemoserver");
        bindService(intent,connection, Context.BIND_AUTO_CREATE);
    }
};

然后在客户端绑定远程服务成功后,给Binder设置死亡代理

private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            IBookManager bookManager = IBookManager.Stub.asInterface(service);
            mRemoteBookManager = bookManager;
            try {
                //为binder设置死亡代理
                mRemoteBookManager.asBinder().linkToDeath(mDeathRecipient,0);
                ......
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        ......
    };

方法二:在客户端的onServiceDisconnected方法中重连远程服务

@Override
public void onServiceDisconnected(ComponentName name) {
    Log.d(TAG, "binder died.");
    mRemoteBookManager = null;
    //重新绑定远程服务
    Intent intent = new Intent();
    intent.setAction("com.xxxx.xxxxx.aidldemoserver.BookManagerService");
    intent.setPackage("com.xxxx.xxxxx.aidldemoserver");
    bindService(intent,connection, Context.BIND_AUTO_CREATE);
}

使用Binder连接池

Binder连接池的主要作用就是将每个业务模块的Binder请求统一转发到远程Service中去执行,从而避免了重复创建Service的过程

在这种模式下,添加了一个Binder连接池起到了类似中转站的作用,将原有的客户端和服务端一对一模式变成了多个客户端和服务端的多对一模式。

Binder连接池和服务端的通讯:

我们只需要提供一个含有查询客户端对应Binder方法的接口(IBinderPool),然后按照传统的一对一模式将Binder连接池绑定到远程服务端上,然后在服务端中实现上述接口的方法(queryBinder)就可以在Binder连接池中调用了。

Binder连接池和客户端的通讯:

这个就简单了,因为Binder连接池本来就在客户端中,直接创建对象调用其方法即可。

接下来看下如何应用:

1.老样子,先来创建AIDL文件包。因为要模拟多业务模块的远程调用,这里就以两个为例:加解密功能模块的ISecurityCenter.aidl以及运算功能模块的ICompute.aidl

package ein.aidlserver.binderlinkpool;

interface ISecurityCenter {
    String encrypt(in String content);
    String decrypt(in String password);
}
package ein.aidlserver.binderlinkpool;

interface ICompute {
    int add(int a, int b);
}

然后再添加一个Binder连接池远程调用的接口文件IBinderPool.aidl

package ein.aidlserver.binderlinkpool;

interface IBinderPool {
    IBinder queryBinder(int binderCode);
}

最后再添加一个BinderCode类,用来标识不同的业务模块

package ein.aidlserver.binderlinkpool;

public class BinderCode {
    public static final int COMPUTE = 1;
    public static final int SECURITY_CENTER = 2;
}

同样别忘了将AIDL文件包拷贝至服务端(客户端)。

2.编辑服务端的代码

首先要做的就是创建客户端各个业务模块所需的Binder类,然后在其中实现功能方法

/**
 * 类描述:加解密功能接口的Binder类
 */
public class SecurityCenterStub extends ISecurityCenter.Stub {
    private static final char SECRET_CODE = '^';
    @Override
    public String encrypt(String content) throws RemoteException {
        char[] chars = content.toCharArray();
        for(int i = 0;i<chars.length;i++){
            chars[i] ^= SECRET_CODE;
        }
        return new String(chars);
    }

    @Override
    public String decrypt(String password) throws RemoteException {
        return encrypt(password);
    }
}
/**
 * 类描述:运算功能接口的Binder类
 */
public class ComputeStub extends ICompute.Stub {
    @Override
    public int add(int a, int b) throws RemoteException {
        return a+b;
    }
}

可以看到,因为我们只有一个服务端了,所以不能像以前那样在服务端中new一个Stub然后在onBind方法中返回它,如下

private Binder mBinder = new ICompute.Stub() {
   ......
};
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}

而是通过后续逻辑判断是哪一个业务模块的请求,然后创建对应的实例再返回。如下,新建一个服务BinderPoolService(注意此时服务的成员变量mBinder是IBinderPool.Stub,也就是Binder连接池要去调用的查询方法queryBinder所在接口的Binder)

public class BinderPoolService extends Service {

    private Binder mBinder = new IBinderPool.Stub() {
        IBinder binder = null;
        @Override
        public IBinder queryBinder(int binderCode) throws RemoteException {
            switch (binderCode){
                case BinderCode.SECURITY_CENTER:
                    binder = new SecurityCenterStub();
                    break;
                case BinderCode.COMPUTE:
                    binder = new ComputeStub();
                    break;
                default:
                    break;
            }
            return binder;
        }
    };
    @Override
    public IBinder onBind(Intent intent) {
        return mBinder;
    }
}

最后为该服务添加一个action

<service
    android:name=".BinderPoolService"
    android:enabled="true"
    android:exported="true">
    <intent-filter>
        <action android:name="com.xxxx.xxxxx.aidldemoserver.BinderPoolService" />
    </intent-filter>
</service>

3.编辑客户端代码(Binder连接池)

public class BinderPool {
    private static final String TAG = "BinderPool";

    private Context mContext;
    private static volatile  BinderPool sInstance;
    private IBinderPool mBinderPool;
    private CountDownLatch mConnectBinderPoolCountDownLatch;
    //死亡代理
    private IBinder.DeathRecipient mDeathRecipient = new IBinder.DeathRecipient() {
        @Override
        public void binderDied() {
            //当binder死亡的时候,重新绑定远程服务
            Log.d(TAG, "binder died.");
            mBinderPool.asBinder().unlinkToDeath(mDeathRecipient,0);
            mBinderPool = null;
            connectBinderPoolService();
        }
    };

    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            mBinderPool = IBinderPool.Stub.asInterface(service);
            try {
                //设置死亡代理
                mBinderPool.asBinder().linkToDeath(mDeathRecipient,0);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            mConnectBinderPoolCountDownLatch.countDown();
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    };
    //构造器
    private BinderPool(Context context){
        this.mContext = context;
        connectBinderPoolService();
    }
    //单例模式
    public static BinderPool getsInstance(Context context){
        if(sInstance == null){
            synchronized (BinderPool.class){
                if(sInstance == null)
                    sInstance = new BinderPool(context);
            }
        }
        return sInstance;
    }
    //绑定服务
    private synchronized void connectBinderPoolService() {
        mConnectBinderPoolCountDownLatch = new CountDownLatch(1);
        Intent intent = new Intent();
        intent.setAction("com.xxxx.xxxxx.aidldemoserver.BinderPoolService");
        intent.setPackage("com.xxxx.xxxxx.aidldemoserver");
        mContext.bindService(intent,connection,Context.BIND_AUTO_CREATE);
        try {
            mConnectBinderPoolCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    //查询业务模块所需Binder的方法
    public IBinder queryBinder(int binderCode){
        IBinder binder = null;
        if(mBinderPool != null){
            try {
                binder = mBinderPool.queryBinder(binderCode);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
        }
        return binder;
    }
    //解绑服务
    public synchronized void unbindService(){
        mContext.unbindService(connection);
    }
}

基本上各个部分的功能都加上注释了。最后提醒一下,bindService这一异步操作通过CountDownLatch转换成了同步操作,这就意味着他有可能是耗时的,再有Binder方法的调用过程也可能是耗时的,因此不能在UI线程中执行上述操作。

4.最后看下客户端各个业务模块的使用

先要初始化我们的Binder连接池binderPool

new Thread(new Runnable() {
    @Override
    public void run() {
        BinderPool binderPool = BinderPool.getsInstance(SecondActivity.this);
    }
}).start();

加解密功能

private void securityCenterTest(){
    IBinder securityStub = binderPool.queryBinder(BinderCode.SECURITY_CENTER);
    ISecurityCenter securityImp = ISecurityCenter.Stub.asInterface(securityStub);
    try {
        String msg = "hello world";
        String psd = securityImp.encrypt(msg);
        Log.d(TAG, "content: "+msg);
        Log.d(TAG, "encrypt: "+psd);
        Log.d(TAG, "decrypt: "+securityImp.decrypt(psd));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

运算功能

private void computeTest(){
    IBinder computeStub = binderPool.queryBinder(BinderCode.COMPUTE);
    ICompute computeImp = ICompute.Stub.asInterface(computeStub);
    try {
        Log.d(TAG, "3 + 5 = "+computeImp.add(3,5));
    } catch (RemoteException e) {
        e.printStackTrace();
    }
}

猜你喜欢

转载自blog.csdn.net/Ein3614/article/details/82770727