一、什么叫观察者模式
观察者模式是常用的设计模式之一;例如在下载文件时,我们可能会更新图标动画,另外在别的控件显示当前下载进度,下载完成后要对文件进行处理,可能这些处理过程都是不同的业务模块,这些模块的生命周期不一样。
简单来说观察者模式可以理解为对多个回调实体对象的管理;
二、简单案例
下面我们通过一个简单的例子来讲解如何设计观察者模式:
本案例是新建一个线程可以设定定时器,在时间到时发送当前的时间,最后把时间反馈给注册者。
首先我们定义回调接口
public interface ObserverCallback {
public void onTime(String textTime);
}
然后在时间触发处管理回调实体
package com.penny.javademo;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class Observer implements Runnable {
private static final String TAG = Observer.class.getSimpleName();
private volatile boolean running = false;
private long interval = 1000;
private List<ObserverCallback> callbacks = new CopyOnWriteArrayList<>();
public Observer(long intervalMillsec) {
if (interval < 1) {
throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);
}
this.interval = intervalMillsec;
}
public void addCallback(ObserverCallback cb) {
if (null == cb) {
return;
}
synchronized (this) { // 防止异步线程同时find然后插入
boolean find = false;
for (ObserverCallback temp : callbacks) {
if (cb == temp) {
find = true;
break;
}
}
if (!find) {
callbacks.add(cb);
}
}
}
public void removeCallback(ObserverCallback cb) {
synchronized (this) {
boolean find = false;
for (ObserverCallback temp : callbacks) {
if (cb == temp) {
find = true;
break;
}
}
if (find) {
callbacks.remove(cb);
}
}
}
public void initEvent() {
synchronized (this) {
if (! running) {
new Thread(this).start();
}
}
}
public void terminate() {
synchronized (this) {
this.running = false;
}
}
@Override
public void run() {
boolean localRun = running;
synchronized (this) {
this.running = true;
localRun = running;
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (localRun) {
// 处理回掉事件
for (ObserverCallback cb : callbacks) {
cb.onTime(df.format(Calendar.getInstance().getTime()));
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
localRun = running;
}
}
synchronized (this) {
this.running = false;
}
}
}
回调管理者,时间触发为定时发送当前时间;
@Test
public void testObserver() {
final String tag = "testObserver";
Observer o = new Observer(2000);
o.addCallback(new ObserverCallback() {
@Override
public void onTime(String textTime) {
LogUtils.debug(tag, "111 textTime: " + textTime);
}
});
o.addCallback(new ObserverCallback() {
@Override
public void onTime(String textTime) {
LogUtils.debug(tag, "222 textTime: " + textTime);
}
});
o.initEvent();
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LogUtils.debug(TAG, "testDemo finish.");
}
输出结果:
D/testObserver 111 textTime: 2019-03-16 15:11:06
D/testObserver 222 textTime: 2019-03-16 15:11:06
D/testObserver 111 textTime: 2019-03-16 15:11:08
D/testObserver 222 textTime: 2019-03-16 15:11:08
D/testObserver 111 textTime: 2019-03-16 15:11:10
D/testObserver 222 textTime: 2019-03-16 15:11:10
D/testObserver 111 textTime: 2019-03-16 15:11:12
D/testObserver 222 textTime: 2019-03-16 15:11:12
D/testObserver 111 textTime: 2019-03-16 15:11:14
D/testObserver 222 textTime: 2019-03-16 15:11:14
D/ObserverTest testDemo finish.
Process finished with exit code 0
上述代码虽然功能完善,但是存在一个问题,假如有观察对象注册了callback然后忘记注销,由于定时器一直持有该对象那么就会导致内存泄漏。
三、WeakReference加持
对于内存泄漏这个问题我们可以通过Java软引用来解决。
1. 首先用一个List来存储所有回调callback,该list用软引用WeakReference方式持有callback;
2. 用第二个List在存储强制类型的回调callback,该list直接持有callback;
3. 注册者在向定时器注册回调时可以设置注册引用类型,如果为强引用,那么步骤1、2的List都加入该对象;如果为软引用,那么只有步骤1的List会加入该对象。
4. 定时器触发事件时只需要遍历步骤1的Lit;
5. 注销某个注册者时需要遍历步骤1、2的callback,然后删除之。
package com.penny.javademo;
import java.lang.ref.WeakReference;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
public class ObserverWeakRef3 implements Runnable {
private static final String TAG = ObserverWeakRef3.class.getSimpleName();
private volatile boolean running = false;
private long interval = 1000;
private List<WeakReference<ObserverCallback>> callbacks = new CopyOnWriteArrayList<>();
private List<ObserverCallback> strongList = new CopyOnWriteArrayList<>();
public ObserverWeakRef3(long intervalMillsec) {
if (interval < 1) {
throw new IllegalArgumentException("interval must be larger than 0. interval: " + interval);
}
this.interval = intervalMillsec;
}
public void addCallback(ObserverCallback cb, boolean isWeakRef) {
if (null == cb) {
return;
}
synchronized (this) { // 防止异步线程同时find然后插入
boolean find = false;
for (WeakReference<ObserverCallback> ref : callbacks) {
if (cb == ref.get()) {
find = true;
break;
}
}
if (!find) {
callbacks.add(new WeakReference<>(cb));
if (!isWeakRef) {
strongList.add(cb);
}
}
}
}
public void removeCallback(ObserverCallback cb) {
synchronized (this) {
boolean find = false;
List<WeakReference<ObserverCallback>> delList = new ArrayList<>();
ObserverCallback localCallback = null;
for (WeakReference<ObserverCallback> ref : callbacks) {
localCallback = ref.get();
if (null == localCallback || cb == localCallback) {
delList.add(ref);
}
if (cb == localCallback) {
find = true;
}
}
if (find) {
strongList.remove(cb);
}
for (WeakReference<ObserverCallback> temp : delList) {
callbacks.remove(temp);
}
delList.clear();
}
}
public void initEvent() {
synchronized (this) {
if (! running) {
new Thread(this).start();
}
}
}
public void terminate() {
synchronized (this) {
this.running = false;
}
}
@Override
public void run() {
boolean localRun = running;
synchronized (this) {
this.running = true;
localRun = running;
}
ObserverCallback localCallback;
DateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
while (localRun) {
// 处理回掉事件
for (WeakReference<ObserverCallback> cb : callbacks) {
localCallback = cb.get();
if (null != localCallback) {
localCallback.onTime(df.format(Calendar.getInstance().getTime()));
} else {
LogUtils.debug(TAG, "localCallback: null");
}
}
try {
Thread.sleep(interval);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (this) {
localRun = running;
}
}
synchronized (this) {
this.running = false;
}
}
}
测试代码:
@Test
public void testObserverWeakRef3() {
final String tag = "testObserverWeakRef3";
ObserverWeakRef2 o = new ObserverWeakRef2(2000);
for (int i = 0; i < 10; i++) {
final String text = String.format("%d%d%d", i, i, i);
o.addCallback(new ObserverCallback() {
byte[] buf = new byte[1024 * 1024 * 10]; // 增大空间,诱发虚拟机回收内存
@Override
public void onTime(String textTime) {
LogUtils.debug(tag, text + " textTime: " + textTime);
}
}, i > 4);
}
o.initEvent();
try {
Thread.sleep(6000);
System.gc(); // 手动触发gc,最终执行还是靠虚拟机的策略
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LogUtils.debug(TAG, "testDemo finish.");
}
输出结果。
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:55
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:57
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 555 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 666 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 777 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 888 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:36:59
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:01
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:01
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:03
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:03
D/testObserverWeakRef3 000 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 111 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 222 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 333 textTime: 2019-03-16 15:37:05
D/testObserverWeakRef3 444 textTime: 2019-03-16 15:37:05
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/ObserverWeakRef3 localCallback: null
D/testObserverWeakRef3 999 textTime: 2019-03-16 15:37:05
D/ObserverTest testDemo finish.
Process finished with exit code 0
通过打印输出可以看到,通过WeakReference软引用的callback在 2019-03-16 15:37:01时就被系统回收了,因此找不到对应的callback。证明用软引用是有效的,可以避免内存泄漏;
四、总结
当然,最重要的还是调用者本身要懂得对注册者的生命周期类型,如果把原本弱周期的注册者放到定时器是使用强引用方式注册,然后又不注销,那么也会导致内存泄漏。对于这种情况我只能说——我也很无奈啊!!!