笔记 - 安卓中的Binder之使用②(三)

引言

上一篇我们讲了一个简单的示例,这篇可能比较短,我们讲一下怎么在之前的基础上进行拓展。

强调一下整个系列的笔记都是对《Android开发艺术探索》的记录,在其之上进行了一点点小的修改,方便理解。超级超级感谢任老师的这本书,大家可以买书去看,很好的一本书。

梗概

这里我们讲一个故事:小明賊喜欢玩游戏,什么游戏都玩,他电脑里面的游戏都是去店里买正版光碟然后再安装的,为了玩到最新的游戏,小明每天都会去店里看有没有出新品。久而久之,小明觉得这亚子好累啊,于是小明就去找店家想想办法,店家说,要不你注册个会员吧,这样每次有新品,我就根据会员资料打电话通知你有什么新游戏。于是小明注册了会员,从此之后不用一直跑来跑去、跑来跑去,玩游戏越来越nb,并结识了富婆,从此走向人生巅峰。
走向人生巅峰表情包
那对于Binder,我们新添加一种功能,客户端可以注册,然后每当图书列表有新的图书,服务端就会通知注册过的客户端“有新书啦,你康康”。客户端也可以在不需要的时候取消这个功能。

这是一种观察者模式,这一篇就根据上面讲的功能来进行实现。

客户端

我们写两个包名不一样但是功能还有代码一模一样的客户端,同时向服务端进行注册,然后在一段进行添加图书,看看两个客户端会不会都收到信息。因为代码一模一样,我就只讲一个了。

AIDL文件

基于上一篇的内容,aidl需要改动的地方有两个,一个是添加一个接口aidl,另一个是在IBookManager中添加两个方法

  • IOnNewBookArrivedListener实现
    实现推送这个功能,我们使用一个回调,然后客户端再进行注册,服务端就可以调用来,因为普通的接口不能实现IPC,所以我们使用aidl来创建一个接口。
import com.example.aidl.bookaidl.Book;

interface IOnNewBookArrivedListener {
   void onNewBookArrived(in Book newBook);
}
  • IBookManager
    因为提供了注册功能,所以我们在这里要添加一个registerListener的注册方法,和一个unregisterListener解除注册的方法
import com.example.aidl.bookaidl.Book;
import com.example.aidl.bookaidl.IOnNewBookArrivedListener;

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

Activity

我们添加两个按钮,一个是注册按钮,一个是解除注册按钮,然后我们需要实现IOnNewBookArrivedListener这个客户端的接口。并添加一个Handler处理收到的信息

  • IOnNewBookArrivedListener实现
    这个方法会运行在客户端的Binder线程池中,所以我们需要使用hander来讲信息传到UI线程中。接着我们再打印一条log,方便之后观察调用步骤
 private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
            Log.i(TAG, "onNewBookArrived");
        }
    };
  • Hander
    handler我们得到消息后打印一条log,把新的图书的信息打印出来
 private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case MESSAGE_NEW_BOOK_ARRIVED:{
                    Log.i(TAG, "receive new book: " + msg.obj);
                    break;
                }
                default:
                    super.handleMessage(msg);
            }
        }
    };
  • 注册按钮
    这里使用获得的bookManager这个binder对象的registerListener函数进行注册,参数就是我们上面实现的IOnNewBookArrivedListener接口
btn_register.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    bookManager.registerListener(mOnNewBookArrivedListener);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        });
  • 解除注册按钮
    跟注册按钮类似,使用bookManager的unregisterListener函数
btn_unregister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    bookManager.unregisterListener(mOnNewBookArrivedListener);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        });
  • onDestory函数
    我们在这里需要进行判断,再解除注册,因为可能存在没有解除注册然后app异常退出的情况,不接触注册的话,会导致服务端一直持有客户端注册的binder,是一种资源的浪费。
@Override
    protected void onDestroy() {
        super.onDestroy();
        if (bookManager != null  && bookManager.asBinder().isBinderAlive()){
            try {
                Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                bookManager.unregisterListener(mOnNewBookArrivedListener);
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
    }
  • 整个Activity的代码
public class MainActivity extends AppCompatActivity {

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

    private Button btn_getBookList;
    private Button btn_addBook;
    private Button btn_bindService;
    private Button btn_register;
    private Button btn_unregister;

    private IBookManager bookManager;

    private ServiceConnection mConnection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            bookManager = IBookManager.Stub.asInterface(service);
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {
            bookManager = null;
            Log.i(TAG, "binder die");
        }
    };

    @SuppressLint("HandlerLeak")
    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(@NonNull Message msg) {
            switch (msg.what){
                case MESSAGE_NEW_BOOK_ARRIVED:{
                    Log.i(TAG, "receive new book: " + msg.obj);
                    break;
                }
                default:
                    super.handleMessage(msg);
            }
        }
    };

    private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
        @Override
        public void onNewBookArrived(Book newBook) throws RemoteException {
            mHandler.obtainMessage(MESSAGE_NEW_BOOK_ARRIVED, newBook).sendToTarget();
            Log.i(TAG, "onNewBookArrived");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn_getBookList = findViewById(R.id.btn_getBookList);
        btn_addBook = findViewById(R.id.btn_addBook);
        btn_bindService = findViewById(R.id.btn_bindService);
        btn_register = findViewById(R.id.btn_register);
        btn_unregister = findViewById(R.id.btn_unregister);

        btn_bindService.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent();
                intent.setComponent(new ComponentName("com.example.aidlservice", "com.example.aidlservice.BookManagerService"));
                bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
            }
        });

        btn_getBookList.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    List<Book> list = bookManager.getBookList();
                    Log.i(TAG, "query book list, list type: " + list.getClass().getCanonicalName());
                    Log.i(TAG, "query book list, list content: " + list.toString());
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        });

        btn_addBook.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    Book book = new Book(3, "Android开发艺术探索");
                    bookManager.addBook(book);
                } catch (RemoteException e) {
                    e.printStackTrace();
                }
            }
        });

        btn_register.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    bookManager.registerListener(mOnNewBookArrivedListener);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        });

        btn_unregister.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                try {
                    bookManager.unregisterListener(mOnNewBookArrivedListener);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        });

    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        if (bookManager != null  && bookManager.asBinder().isBinderAlive()){
            try {
                Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
                bookManager.unregisterListener(mOnNewBookArrivedListener);
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }
        unbindService(mConnection);
    }
}
  • 整个客户端的项目结构
    客户端的项目结构

服务端

服务端我们做的修改有:aidl文件、Service类

AIDL

服务端aidl文件做的修改跟客户端是一样的,这里就不再详细说明了

BookManagerService

在Service我们需要做的是维护一个回调列表,修改服务端binder代码

注意:这里使用回调列表的方式是有问题的,后面会针对这个问题进行修改

  • 回调列表
    当客户端注册的时候,会将他们的传过来的Binder对象添加进去,当有新书到达的时候,就可以遍历列表并调用每一个Binder的方法进行通知了。
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();
  • 服务端binder
  1. 注册功能
    我们先判断回调列表中是否已经有注册过参数的回调,如果没有,就将参数的回调添加进列表中,并打印相关log日志
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
	if(!mListenerList.contains(listener)){
	    mListenerList.add(listener);
	    Log.i(TAG, "register listener succeed.");
	}else {
	    Log.i(TAG, "already exists.");
	}
	Log.i(TAG, "registerListener, size:" + mListenerList.size());
}
  1. 解除注册功能
    我们先判断回调列表中是否已经有注册过参数的回调,如果有,就将参数的回调添加进列表中,并打印相关log日志
public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
      if(!mListenerList.contains(listener)){
          mListenerList.remove(listener);
          Log.i(TAG, "unregister listener succeed.");
      }else {
          Log.i(TAG, "not found, can not unregister");
      }
      Log.i(TAG, "unregisterListener, size:" + mListenerList.size());
}
  1. addBook方法
    这里每当添加了一本新的图书,我们需要遍历回调列表中的listener,并调用他们的onNewBookArrived()方法。
public void addBook(Book book) throws RemoteException {
    mBookList.add(book);//添加图书
    onNewBookArrived(book);
}

private void onNewBookArrived(Book book) throws RemoteException{
    mBookList.add(book);
    Log.i(TAG, "onNewBookArrived, notify listeners:" + mListenerList.size());
    for (int i = 0; i < mListenerList.size(); i++) {
        IOnNewBookArrivedListener listener = mListenerList.get(i);
        Log.i(TAG, "onNewBookArrived, notify listener:" + listener);
        listener.onNewBookArrived(book);
    }
}
  • 整个BookManagerService代码
public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();//这个数据结构支持并发读/写
    private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListenerList = new CopyOnWriteArrayList<>();

    //在服务端中创建Binder对象并实现接口中的方法
    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);//添加图书
            onNewBookArrived(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(!mListenerList.contains(listener)){
                mListenerList.add(listener);
                Log.i(TAG, "register listener succeed.");
            }else {
                Log.i(TAG, "already exists.");
            }
            Log.i(TAG, "registerListener, size:" + mListenerList.size());
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
            if(!mListenerList.contains(listener)){
                mListenerList.remove(listener);
                Log.i(TAG, "unregister listener succeed.");
            }else {
                Log.i(TAG, "not found, can not unregister");
            }
            Log.i(TAG, "unregisterListener, size:" + mListenerList.size());
        }


    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "AIDL Service is created");
        //图书列表中默认添加有两本书
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return mBinder;//返回Binder对象
    }

    private void onNewBookArrived(Book book) throws RemoteException{
        mBookList.add(book);
        Log.i(TAG, "onNewBookArrived, notify listeners:" + mListenerList.size());
        for (int i = 0; i < mListenerList.size(); i++) {
            IOnNewBookArrivedListener listener = mListenerList.get(i);
            Log.i(TAG, "onNewBookArrived, notify listener:" + listener);
            listener.onNewBookArrived(book);
        }
    }
}

Binder演示

  • 我们先开启服务端AIDLService,然后分别开启两个客户端AIDLClient、ADILClient2
    准备工作
  • 两个客户端获取服务端的Binder
    获取服务端的Binder
  • 我们先获取一遍图书列表
    图书列表
  • 两个客户端进行注册
    注册
  • 在AIDLClient进行添加书籍,AIDLClient2被通知有新的图书通知

问题

上面说过这样实现是有问题的,这里就来分析一下,我们演示一下,然后再来讲一下故事嘻嘻嘻嘻,最后进行分析

演示

基于上面之后,我们在AIDLClient2解除注册,按理服务端的列表回去掉客户端2注册的回调,客户端2也不会再收到新书信息,但是,实际情况是这样的

  • AIDLClient2按下解除注册后,服务端的log
    问题演示1
  • AIDLClient添加书籍,客户端2打印的log
    问题演示2
    我们发现事情并没有那么简单

故事

结识富婆的小明原本想着这辈子一帆风顺,每天过着白天打游戏,死肥宅,晚上运动一下的美好生活,但是有一天富婆急了,说小明总是打游戏没有上进心,要跟他分手。
富婆不要我了
于是小明决定放弃游戏,他把之前注册的会员卡剪掉了,觉得这样就可以注销会员,不会收到新游戏的消息,可是在这之后,小明每天还是会收到新游戏的消息。小明火烧眉毛,万分着急,日渐消瘦,似乎被榨干

这个时候,一个帅气的男人走了过去,他出现了,那就是我了哈哈哈哈哈哈。
帅气的我
我告诉小明,你单方面减掉会员卡是没有用的,楼下店家那里还有你的会员信息,这是俩个是不同的东西,商家不知道你注销了会员,你去打电话去跟商家说一下才可以。

于是,小明真正的注销了会员,上进得要命,和富婆和好了,过上了幸福美满的生活,死肥宅从竹竿弟的蜕变由此开始了。

分析

原本我们注册和解注册过程中使用的是同一个客户端对象,但是通过Binder跨进程通信后到达服务端后,其实产生了两个全新的对象,对象跨进程传输本质上是反序列化的过程,这就是为什么要实现Parcelable接口的原因了。客户端跟服务端的对象其实是两个不同的对象,这就是问题所在。

解决方式

使用RemoteCallbackList,它是专门用来删除跨进程listener接口的。它的内部有一个Map结构专门用来保存所有AIDL的回调,这个Map的key是IBinder类型,value是Callback类型。这样,我们就可以根据Binder对象来解除注册对应的回调了。

另外,它还可以在客户端进程终止后,自动移除客户端注册的listener

服务端

  • 使用RemoteCallbackList维护注册的回调
private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();
  • 修改registerListener()函数
public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
   	mListenerList.register(listener);
    Log.i(TAG, "registerListener");
}

我们直接调用RemoteCallbackList的register函数,传入参数listener进行注册,并打印log日志

  • 修改unregisterListener()函数
 public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
  	mListenerList.unregister(listener);
   	Log.i(TAG, "unregisterListener");
}

我们直接调用RemoteCallbackList的unregister函数,传入参数listener解除注册,并打印log日志

  • 修改onNewBookArrived()函数
private void onNewBookArrived(Book book) throws RemoteException{
    Log.i(TAG, "onNewBookArrived");
    mBookList.add(book);
    final int N = mListenerList.beginBroadcast();
    for (int i = 0; i < N; i++) {
        IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
        if (listener != null){
            try {
                listener.onNewBookArrived(book);
            }catch (RemoteException e){
                e.printStackTrace();
            }
        }
    }
    mListenerList.finishBroadcast();
}

先打印日志,再进行遍历,并依次调用回调的函数,这里注意一下我们遍历的时候必须配对的使用beginBroadcast和finishBroadcast函数,哪怕只是获取一下元素的个数

  • 整个服务端Service函数
public class BookManagerService extends Service {

    private static final String TAG = "BookManagerService";

    private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<>();//这个数据结构支持并发读/写
    private RemoteCallbackList<IOnNewBookArrivedListener> mListenerList = new RemoteCallbackList<>();

    //在服务端中创建Binder对象并实现接口中的方法
    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);//添加图书
            onNewBookArrived(book);
        }

        @Override
        public void registerListener(IOnNewBookArrivedListener listener) throws RemoteException {
            mListenerList.register(listener);
            Log.i(TAG, "registerListener");
        }

        @Override
        public void unregisterListener(IOnNewBookArrivedListener listener) throws RemoteException {
           mListenerList.unregister(listener);
           Log.i(TAG, "unregisterListener");
        }


    };

    @Override
    public void onCreate() {
        super.onCreate();
        Log.i(TAG, "AIDL Service is created");
        //图书列表中默认添加有两本书
        mBookList.add(new Book(1, "Android"));
        mBookList.add(new Book(2, "Ios"));
    }

    @Override
    public IBinder onBind(Intent intent) {
        Log.i(TAG, "onBind");
        return mBinder;//返回Binder对象
    }

    private void onNewBookArrived(Book book) throws RemoteException{
        Log.i(TAG, "onNewBookArrived");
        mBookList.add(book);
        final int N = mListenerList.beginBroadcast();
        for (int i = 0; i < N; i++) {
            IOnNewBookArrivedListener listener = mListenerList.getBroadcastItem(i);
            if (listener != null){
                try {
                    listener.onNewBookArrived(book);
                }catch (RemoteException e){
                    e.printStackTrace();
                }
            }
        }
        mListenerList.finishBroadcast();
    }
}

演示

  • 我们先启动三个app,分别是AIDLService、AIDLClient、AIDLClient2
    演示1
    看到日志表示服务已开启了

  • 在AIDLClient和AIDLClient2中获取服务端的Binder对象
    演示2
    看到日志表示已经获取到binder了

  • 在AIDLClient和AIDLClient2中获取图书列表信息
    演示3
    演示4
    获取成功

  • 在AIDLClient和AIDLClient2中向服务端发起注册
    演示5
    服务端打印了两次注册请求

  • 客户端AIDLClient添加一本图书
    演示6
    演示7
    服务端onNewBookArrived执行,客户端AIDLClient2收到新的图书信息

  • 客户端AIDLClient2解除注册
    演示8
    服务端解除过程执行

  • 客户端AIDLClient再添加一本新的图书
    演示9
    演示10
    AIDLClient收到了新的图书,AIDLClient2没有收到

结语

到此为止,这一篇就完结啦,我们让小明又一次回到了富婆的怀抱,举国欢庆,皆大欢喜

这篇的代码我会放到github上,其中服务端有两个Service,2那个是有问题的Service,另一个是我们改进的Service

这里在强调一下
存在一种可能客户端获取不到服务端的Binder,然后点击获取图书列表时闪退,这是因为安卓系统的省电机制把服务杀掉了。解决方法是锁住服务端并且设置服务端的电池优化策略,具体设置根据系统的不同也都不一样,大家可以百度一下

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值