引言
上一篇我们讲了一个简单的示例,这篇可能比较短,我们讲一下怎么在之前的基础上进行拓展。
强调一下整个系列的笔记都是对《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
- 注册功能
我们先判断回调列表中是否已经有注册过参数的回调,如果没有,就将参数的回调添加进列表中,并打印相关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());
}
- 解除注册功能
我们先判断回调列表中是否已经有注册过参数的回调,如果有,就将参数的回调添加进列表中,并打印相关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());
}
- 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
- 我们先获取一遍图书列表
- 两个客户端进行注册
- 在AIDLClient进行添加书籍,AIDLClient2被通知有新的图书
问题
上面说过这样实现是有问题的,这里就来分析一下,我们演示一下,然后再来讲一下故事嘻嘻嘻嘻,最后进行分析
演示
基于上面之后,我们在AIDLClient2解除注册,按理服务端的列表回去掉客户端2注册的回调,客户端2也不会再收到新书信息,但是,实际情况是这样的
- AIDLClient2按下解除注册后,服务端的log
- AIDLClient添加书籍,客户端2打印的log
我们发现事情并没有那么简单
故事
结识富婆的小明原本想着这辈子一帆风顺,每天过着白天打游戏,死肥宅,晚上运动一下的美好生活,但是有一天富婆急了,说小明总是打游戏没有上进心,要跟他分手。
于是小明决定放弃游戏,他把之前注册的会员卡剪掉了,觉得这样就可以注销会员,不会收到新游戏的消息,可是在这之后,小明每天还是会收到新游戏的消息。小明火烧眉毛,万分着急,日渐消瘦,似乎被榨干
这个时候,一个帅气的男人走了过去,他出现了,那就是我了哈哈哈哈哈哈。
我告诉小明,你单方面减掉会员卡是没有用的,楼下店家那里还有你的会员信息,这是俩个是不同的东西,商家不知道你注销了会员,你去打电话去跟商家说一下才可以。
于是,小明真正的注销了会员,上进得要命,和富婆和好了,过上了幸福美满的生活,死肥宅从竹竿弟的蜕变由此开始了。
分析
原本我们注册和解注册过程中使用的是同一个客户端对象,但是通过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
看到日志表示服务已开启了 -
在AIDLClient和AIDLClient2中获取服务端的Binder对象
看到日志表示已经获取到binder了 -
在AIDLClient和AIDLClient2中获取图书列表信息
获取成功 -
在AIDLClient和AIDLClient2中向服务端发起注册
服务端打印了两次注册请求 -
客户端AIDLClient添加一本图书
服务端onNewBookArrived执行,客户端AIDLClient2收到新的图书信息 -
客户端AIDLClient2解除注册
服务端解除过程执行 -
客户端AIDLClient再添加一本新的图书
AIDLClient收到了新的图书,AIDLClient2没有收到
结语
到此为止,这一篇就完结啦,我们让小明又一次回到了富婆的怀抱,举国欢庆,皆大欢喜
这篇的代码我会放到github上,其中服务端有两个Service,2那个是有问题的Service,另一个是我们改进的Service
这里在强调一下
存在一种可能客户端获取不到服务端的Binder,然后点击获取图书列表时闪退,这是因为安卓系统的省电机制把服务杀掉了。解决方法是锁住服务端并且设置服务端的电池优化策略,具体设置根据系统的不同也都不一样,大家可以百度一下