观察者模式浅谈篇(二)
上一篇讲解了简单的观察者模式的使用方法,我们回顾一下它的两大步骤:建立被观察者(observable)【订阅接口,取消订阅接口,数据更新通知接口】、建立观察者(observer)这样就能实时获取被观察者的变化数据。这一篇我主要是分析进程之间使用观察者模式需要注意什么?以及多线程注册与解除注册注意的坑。
【附件:http://blog.csdn.net/qq_34071798/article/details/53731008(观察者模式浅谈篇(一))】
进程之间观察者模式使用注意事项(其中涉及到了进程之间通信知识点,如果对这方面不太懂的我后面博客会和大家分享):
需求:“展示”(或获取)“图书馆”实时更新数据。【注:“展示”是与“图书馆”不同的进程】
代码实现:【“图书馆”进程代码逻辑】注:主要是通过观察者模式储存跨进程的“观察者”信息,开启子线程产生新书,然后通知观察者。package com.example.multiprocessservicesdemo;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicBoolean;
import android.app.Service;
import android.content.Intent;
import android.os.Binder;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.multiprocessservicesdemo.aidl.Book;
import com.example.multiprocessservicesdemo.aidl.IBookManager;
import com.example.multiprocessservicesdemo.aidl.IOnNewBookArrivedListener;
public class BookManagerServices extends Service {
private static final String TAG = "25"
+ BookManagerServices.class.getSimpleName();
// CopyOnWriteArrayList支持并发读/写,当出现多线程访问时该集合会自动线层同步(保证多线程访问安全)
private CopyOnWriteArrayList<Book> mBookList = new CopyOnWriteArrayList<Book>();
private CopyOnWriteArrayList<IOnNewBookArrivedListener> mListener = new CopyOnWriteArrayList<IOnNewBookArrivedListener>();
private AtomicBoolean mIsServiceDestoryed = new AtomicBoolean(false);
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 listerner)
throws RemoteException {
if (!mListener.contains(listerner)) {
mListener.add(listerner);
} else {
Log.d(TAG, "already exists");
}
}
// 取消注册“图书馆”观察者
@Override
public void unregisterListener(IOnNewBookArrivedListener listerner)
throws RemoteException {
if (mListener.contains(listerner)) {
mListener.remove(listerner);
Log.d(TAG, "unregister listener successed");
} else {
Log.d(TAG, "not found,can not unregister.");
}
Log.d(TAG, "unregisterListener,current size:" + mListener.size());
}
};
@Override
public IBinder onBind(Intent arg0) {
return mBinder;
}
@Override
public void onCreate() {
super.onCreate();
mBookList.add(new Book(1, "Android"));
mBookList.add(new Book(2, "IOS"));
new Thread(new ServiceWorker()).start();
}
@Override
public void onDestroy() {
super.onDestroy();
mIsServiceDestoryed.set(true);
}
// 开启一个子线程每过5秒产生一本新书至图书馆
private class ServiceWorker implements Runnable {
@Override
public void run() {
while (!mIsServiceDestoryed.get()) {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
int bookId = mBookList.size() + 1;
Book newBook = new Book(bookId, "new Book#" + bookId);
try {
onNewBookArrived(newBook);
} catch (RemoteException e) {
e.printStackTrace();
}
}
}
}
// 当有新书产生时会通知每个订阅者图书的信息
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
Log.i(TAG, "onNewBookArrived,notify listeners:" + mListener.size());
for (IOnNewBookArrivedListener mOnNewBook : mListener) {
mOnNewBook.onNewBookArrived(book);
}
}
}
代码实现:【客户端进程 订阅“图书馆”信息】注:主要是绑定服务端,建立通讯,然后通过观察者模式获取图书馆实时信息。
package com.example.multiprocessservicesdemo;
import java.util.List;
import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import com.example.multiprocessservicesdemo.aidl.Book;
import com.example.multiprocessservicesdemo.aidl.IBookManager;
import com.example.multiprocessservicesdemo.aidl.IOnNewBookArrivedListener;
public class MainActivity extends Activity {
private static final String TAG = "25" + MainActivity.class.getSimpleName();
// 观察者对象
private IBookManager mRemoteBookManager;
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName className) {
mRemoteBookManager = null;
}
@Override
public void onServiceConnected(ComponentName className, IBinder service) {
IBookManager bookManager = IBookManager.Stub.asInterface(service);
mRemoteBookManager = bookManager;
try {
List<Book> list = bookManager.getBookList();
// 订阅“图书馆”数据更新
bookManager.registerListener(mOnNewBookArrivedListener);
Log.i(TAG, "query book list,list type:"
+ list.getClass().getCanonicalName());
Log.i(TAG, "query book list, list type:" + list.toString());
} catch (RemoteException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
};
// 当有新的图书产生时会调用这个方法告知观察者
private IOnNewBookArrivedListener mOnNewBookArrivedListener = new IOnNewBookArrivedListener.Stub() {
@Override
public void onNewBookArrived(Book newBook) throws RemoteException {
Log.i(TAG, "newBook:" + newBook);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Intent intent = new Intent(this, BookManagerServices.class);
// 和“图书馆”进程建立通讯
bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
if (mRemoteBookManager != null
&& mRemoteBookManager.asBinder().isBinderAlive()) {
try {
// 取消订阅“图书馆”数据更新
mRemoteBookManager
.unregisterListener(mOnNewBookArrivedListener);
Log.i(TAG, "unregister listener:" + mOnNewBookArrivedListener);
} catch (RemoteException e) {
e.printStackTrace();
}
}
// 解除与图书馆之间的通讯
unbindService(mConnection);
super.onDestroy();
}
}
打印结果:注:PID:3285为服务端进程、PID:3261为客户端进程
从日志中可以看出:
每过5秒服务端会产生一本新书并且客户端通过观察者模式接收到了该条新书信息【第一本新书为:Book@713d6cd,第二本为:Book@e731b82】。
具体的多进程观察者模式代码实现就如上面,或许你会提问:那你说的多进程注意事项呢?
多进程的注意事项:
当我们开启这个程序时按返回键会出现下面这段打印:
看上面有两段打印读者应该和我一样有点迷茫:为啥明明Listener没有清空却说找不到?
从代码中:bookManager.registerListener(mOnNewBookArrivedListener);注册与:
mRemoteBookManager.unregisterListener(mOnNewBookArrivedListener);明明是同一个对象为啥说不能解除注册。
首先我们利用程序员思维:代码肯定不会撒谎,其中有Bug;
首先我先简要提一下进程之间通信对象传递:进程之间对象传递不是传的对象真正的本身,而是把传递过来的对象重新克隆了。这其中就是通过反序列化克隆的。提了这一段是不是有点醍醐灌顶,虽然注册与解除都是传递的mOnNewBookArrivedListener但是是不相同的对象。我们只要让注册,解除传到服务端是同一对象就解决问题了。
下面我来介绍一个List集合:RemoteCallbackList【主要用于删除跨进程Listener的接口,而且当客户端进程终止后,它能自动移除客户端注册的Listener,也有多线程访问安全功能】
RemoteCallbackList工作原理很简单:
ArrayMap<IBinder, Callback> mCallbacks = new ArrayMap<IBinder, Callback>();
是用的一个Map结构专门保存AIDL回调,键是IBind,值是Callback
其中键和值获取方式是如下:
IBinder key = listener.asBinder();
Callback value = new Callback(listener,cookie);
关键点就在Key这里因为跨进程对象传输,虽然产生对象不同,但是这些对象底层的Bind对象是同一个,只要相同对象传输,Key是一定唯一,而且listener信息是存储在Map值中。这样通过这个集合就能保证 注册,解除传到服务端是同一对象。
代码改动如下:
创建观察者集合(基本格式和List相同)
private RemoteCallbackList<IOnNewBookArrivedListener> mListener = new RemoteCallbackList<IOnNewBookArrivedListener>();
订阅观察者
// 注册“图书馆”观察者
@Override
public void registerListener(IOnNewBookArrivedListener listerner)
throws RemoteException {
mListener.register(listerner);
}
取消订阅观察者
// 取消注册“图书馆”观察者
@Override
public void unregisterListener(IOnNewBookArrivedListener listerner)
throws RemoteException {
boolean unregister = mListener.unregister(listerner);
Log.i(TAG, "unregisterListener() unregister:"+unregister);
}
// 当有新书产生时会通知每个订阅者图书的信息
private void onNewBookArrived(Book book) throws RemoteException {
mBookList.add(book);
final int N = mListener.beginBroadcast();
for (int i = 0; i < N; i++) {
IOnNewBookArrivedListener lOnNewBook = mListener.getBroadcastItem(i);
if (lOnNewBook != null) {
lOnNewBook.onNewBookArrived(book);
}
}
mListener.finishBroadcast();
}
日志打印如下:(从日志看出我打开应用按了返回键,依旧能够成功解除注册)
这里注意:
RemoteCallbackList与List集合不同之处是“遍历”。RemoteCallbackList不管是获取集合大小还是遍历记得前面首先要调用beginBroadCast方法,最后调用finishBroadCast()方法。
以上介绍了多进程观察者模式使用方式以及注意事项,多线程访问观察者模式时注册与解除注册,如果是同一进程则可以使用CopyOnWriteArrayList集合这样保证线层安全,多进程则使用上述用法,由于考虑把Java.uitl.Observer加入这其中讲解篇幅比较多,所以我将在下一篇与大家分享观察者模式三,让大家对观察者模式有深刻理解。
谢谢品读!