Fork me on GitHub

Android之观察者模式

写在前面

以前听说过一句话,说是 自己写过代码总量没有超过10w行谈设计模式那都是耍流氓 。我信了,所以一直没怎么系统的看已经买了的《Android源码设计模式》。最近有个小伙伴在群里问recyclerview怎么刷新数据,以前大概也做过,流程也就是那么两步:1.更新Adapter里数据集的引用,让他指向最新的数据集。2.调用Adapter的notifyDataSetChanged()来更新ui。之后小伙伴又问了notifyDataSetChanged()到底如何更新ui的,当时只是看出了一个观察者模式,还有一些细节没想明白。而且讲真的观察者模式的应用还是非常多的,无论是Android还是最近很火的RxJava,其中都可以看到观察者模式的身影,所以决定这周把观察者模式撸一遍。

1、什么是观察者模式

观察者模式(Observer Pattern)定义了对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新,观察者模式又叫做发布-订阅(Publish/Subscribe)模式。

定义总是这么精准而抽象,让我们结合一些Android中的场景来简单的理解一下:在Android最常用的点击事件,我们会通过设置控件的OnClickListener并传入一个OnClickListener的实现类来回调点击事件。这种我们便可以将之看做一个观察者模式。我们的OnClickListener是观察者,被观察者是控件,当有点击事件的时候控件发布点击事件,观察者OnClickListener就会接收到点击事件。当然了,说到底,就是回调。

2、用回调写一个简单的观察者模式

首先我们想一个生活中的场景,来凑个观察者模式出来。平时我们要烧水吧,总要派个人看着,水烧开的时候把电源拔了装水。在这个场景里水壶就是被观察者(Observable),观察者(Observer)就是人。首先我们用以上我说的,那玩意就是回调,用回调来写一个看看。那我们先写一个被观察者“水壶”来看看:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
public class Kettle<T> {
Observer<T> o;

/**
* 发布信息
*/
public void publishEvent(T t){
if (o == null)
throw new NullPointerException("you must regist Observer first!");
notifyData(t);
}

/**
* 通知订阅者
*/
public void notifyData(T t){
o.receiverEvent(t);
}

/**
* 注册一个观察者
*/
public void registObserver(Observer<T> o){
this.o = o;
}

/**
* 在你需要的时候调用这个方法,防止内存泄露
*/
public void unregistObserver(){
this.o = null;
}
}

首先我们这个水壶是被观察者,内部肯定要维护一个观察者的引用,或者一个观察者队列的引用,方便我们进行回调,当然更多的事我们尽量不要通过Observer这个东西来做,在这个观察者模式中我希望Observer仅仅作为一个纯粹的回调。因为观察模式本身的特性之一就是解耦,如果你要通过Observer干更多的事无疑会加重Observable和Observer之间的耦合。更多信息可以看代码,我注释已经写得很详细了。

接下来看看上面提到的那个Observer我是咋写的:

1
2
3
public interface Observer<T> {
void receiverEvent(T t);
}

很简单的一个接口,写上泛型,嗯,顺便练习一下泛型…只有一个方法,用来回调。有接口那我们肯定要有实现类,我这个场景里说了,人是观察者,于是我写了一个Observer的实现类:

1
2
3
4
5
6
7
8
9
10
11
12
public abstract class People implements Observer<String> {
@Override
public void receiverEvent(String s) {
System.out.println(s);
dealWithEvent();
}

/**
* 交给用户去处理事件
*/
public abstract void dealWithEvent();
}

我把这个People设计为一个抽象类,这样我可以在接收到这个事件的时候做一些简单的处理(把他打印出来……),然后再把具体的逻辑交给这个抽象类的子类来做,我这逻辑比较简单,就没传什么参数进去了。最后免不了跑起来看看了~

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class Test {
public static void main(String[] args){
//水壶
Kettle<String> kettle = new Kettle<>();

People people = new People() {
@Override
public void dealWithEvent() {
System.out.println("People:拔电源装水了~");
}
};

//注册观察者
kettle.registObserver(people);
//在一定条件下调用此方法发布事件
kettle.publishEvent("Kettle:水烧开了!再不拔电源我要炸了!");
}
}

运行结果

这里实现了一个加单的观察者模式,观察者也只能注册一个,不过例子么,简单的才容易看懂嘛~接下来看一下Java util里自带的Observable和Observer,看一下别人的套路~

3、Java中的观察者模式

在Java的util包里也有Observable和Observer那么这俩兄弟跟我们上面自己实现的有啥不同呢?首先还是那个水壶的例子,有了上面的基础,我就直接把所有的类和测试代码甩上来了,相信以各位看官的实力都是小case:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class HelloWorld {
public static native String sayHello(String name);

public static void main(String[] args) {
//被观察者
Kettle kettle = new Kettle();
//观察者
PeopleLookKettle people = new PeopleLookKettle();

kettle.addObserver(people);
kettle.notifyPeople("kettle:水烧开了!再不拔电源我要炸了!!");

}
}

public class Kettle extends Observable {
public void notifyPeople(String str){
System.out.println("kettle:我是水壶~");
setChanged();
notifyObservers(str);
}
}

public class PeopleLookKettle implements Observer {
@Override
public void update(Observable o, Object arg) {
System.out.println((String) arg);
System.out.println("People:拔电源装水~");
}
}

之后还是看一下运行结果

运行结果

代码上完了,那么这里实现的观察模式又是个什么套路呢?不比比直接看源码,先从简单的Observer看起:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Observer {
/**
* This method is called whenever the observed object is changed. An
* application calls an <tt>Observable</tt> object's
* <code>notifyObservers</code> method to have all the object's
* observers notified of the change.
*
* @param o the observable object.
* @param arg an argument passed to the <code>notifyObservers</code>
* method.
*/
void update(Observable o, Object arg);
}

我去,这跟我上面的设计不符啊…这货怎么把Observable传过来了…这只是设计类和接口的一些设计理念不一样,作为jdk他需要考虑各种兼容性和安全性的问题,所以不可能像我们客户端程序员一样,很多时候写的都非常任性。先不扯那么多,我们要看的是套路~这玩意是个接口,就像我说的那样,做个回调就行了,剩下的都交给实现类来操心。

看完了Observer我们再来看看Observable:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
public class Observable {
private boolean changed = false;
private Vector<Observer> obs;

/** Construct an Observable with zero Observers. */

public Observable() {
obs = new Vector<>();
}

/**
* Adds an observer to the set of observers for this object, provided
* that it is not the same as some observer already in the set.
* The order in which notifications will be delivered to multiple
* observers is not specified. See the class comment.
*
* @param o an observer to be added.
* @throws NullPointerException if the parameter o is null.
*/
public synchronized void addObserver(Observer o) {
if (o == null)
throw new NullPointerException();
if (!obs.contains(o)) {
obs.addElement(o);
}
}

/**
* Deletes an observer from the set of observers of this object.
* Passing <CODE>null</CODE> to this method will have no effect.
* @param o the observer to be deleted.
*/
public synchronized void deleteObserver(Observer o) {
obs.removeElement(o);
}

/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to
* indicate that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and <code>null</code>. In other
* words, this method is equivalent to:
* <blockquote><tt>
* notifyObservers(null)</tt></blockquote>
*
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers() {
notifyObservers(null);
}

/**
* If this object has changed, as indicated by the
* <code>hasChanged</code> method, then notify all of its observers
* and then call the <code>clearChanged</code> method to indicate
* that this object has no longer changed.
* <p>
* Each observer has its <code>update</code> method called with two
* arguments: this observable object and the <code>arg</code> argument.
*
* @param arg any object.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#hasChanged()
* @see java.util.Observer#update(java.util.Observable, java.lang.Object)
*/
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;

synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}

for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}

/**
* Clears the observer list so that this object no longer has any observers.
*/
public synchronized void deleteObservers() {
obs.removeAllElements();
}

/**
* Marks this <tt>Observable</tt> object as having been changed; the
* <tt>hasChanged</tt> method will now return <tt>true</tt>.
*/
protected synchronized void setChanged() {
changed = true;
}

/**
* Indicates that this object has no longer changed, or that it has
* already notified all of its observers of its most recent change,
* so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
* This method is called automatically by the
* <code>notifyObservers</code> methods.
*
* @see java.util.Observable#notifyObservers()
* @see java.util.Observable#notifyObservers(java.lang.Object)
*/
protected synchronized void clearChanged() {
changed = false;
}

/**
* Tests if this object has changed.
*
* @return <code>true</code> if and only if the <code>setChanged</code>
* method has been called more recently than the
* <code>clearChanged</code> method on this object;
* <code>false</code> otherwise.
* @see java.util.Observable#clearChanged()
* @see java.util.Observable#setChanged()
*/
public synchronized boolean hasChanged() {
return changed;
}

/**
* Returns the number of observers of this <tt>Observable</tt> object.
*
* @return the number of observers of this object.
*/
public synchronized int countObservers() {
return obs.size();
}
}

可以看出Observable内部使用了一个Vector来维护订阅的Observer,关于Vector这里不做更多的了解,在这就把他当做一个普通的Observer容器就行了。让我们看看和这个容器有关的套路,为了防止各位产生代码疲劳,我特意贴心的给各位截了个图23333333:

套路

添加和删除Observer就是在容器obs里做增删操作,这套路很简单,不过为了线程安全加了个synchronized。之后看一下重点,通知Observers时调用的notifyObservers(),notifyObservers()最终会调用他自身带参的重载方法,看下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public void notifyObservers(Object arg) {
/*
* a temporary array buffer, used as a snapshot of the state of
* current Observers.
*/
Object[] arrLocal;

synchronized (this) {
/* We don't want the Observer doing callbacks into
* arbitrary code while holding its own Monitor.
* The code where we extract each Observable from
* the Vector and store the state of the Observer
* needs synchronization, but notifying observers
* does not (should not). The worst result of any
* potential race-condition here is that:
* 1) a newly-added Observer will miss a
* notification in progress
* 2) a recently unregistered Observer will be
* wrongly notified when it doesn't care
*/
if (!changed)
return;
arrLocal = obs.toArray();
clearChanged();
}

for (int i = arrLocal.length-1; i>=0; i--)
((Observer)arrLocal[i]).update(this, arg);
}

先除去那大段的注释不看,这里就是拿到Observable内部维护的Observer容器,然后遍历回调这些Observer的update方法以实现让所有Observer收到通知。但是后面那段代码被执行是有条件的,就是Observable内部的changed字段为true才会执行,而这个字段只有通过setChanged()方法来将其值置为true。但是在上面的源码中我们可以发现这个方法是protected修饰的,所以不通过特殊手段的话,我们只有通过继承才能来调用这个方法了。所以在我以上的实现代码中是有一个继承于Observable的类的。

回头再看看那段注释(自己的烂翻译…有错请指出…):
我们不想让Observer在持有他自己的监听时在回调任意代码。抽取这段代码存储存储Observer需要同步的状态,但是并不通知这些Observer。任何潜在的竞争条件可能会导致的最坏情况是:

  • 新添加的Observer将会错过一个正在进行的通知
  • 最近被解除注册的Observer可能会错误的同步一个他不关心的玩意

这些东西说实话,我只是有一点点想法,并不能说的很清楚。我觉得是多线程情况下这段代码需要加上一个同步锁,不然可能会引发他注释里写的那两点糟糕的情况。我接触的多线程还是有点少的,所以这段我就先这么翻着,而且这对我们理解观察者模式的套路并没有非常大的影响。

分析完了Java中的观察者模式,接下来回到文章最前面提到的那个问题,RecyclerView中的ui更新到底是咋回事。

4、RecyclerView中的ui更新

其实要弄清楚这个首先得看Adapter,因为Adapter才是掌控数据集的那个。那么让我们来看一下RecyclerView.Adapter的nonotifyDataSetChanged()方法

1
2
3
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}

继续追踪这个源码,看看咋回事

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
static class AdapterDataObservable extends Observable<AdapterDataObserver> {
public boolean hasObservers() {
return !mObservers.isEmpty();
}

public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}

//其余方法省略
}

可以看到notifyChanged()这个方法里就两行代码,经过上面的一番学习,我闭着眼睛也能猜到mObservers是一个Observer的集合,通过遍历的去调用onChanged,然后这个onChanged是回调。既然知道这一点,那么我们就需要在RecyclerView中找到Observer的具体实现类,不过在此之前我们找孩子之前得先找他爸~很简单,追踪onChanged()的源码,看看到底是谁的方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static abstract class AdapterDataObserver {
public void onChanged() {
// Do nothing
}

public void onItemRangeChanged(int positionStart, int itemCount) {
// do nothing
}

public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
// fallback to onItemRangeChanged(positionStart, itemCount) if app
// does not override this method.
onItemRangeChanged(positionStart, itemCount);
}

public void onItemRangeInserted(int positionStart, int itemCount) {
// do nothing
}

public void onItemRangeRemoved(int positionStart, int itemCount) {
// do nothing
}

public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
// do nothing
}
}

找到了,都是空方法,你可能回说这还不是接口,没事,接口能做的抽象类也能做,我们只要找这个抽象类的孩子就行了。鸡贼的我果断ctrl+f输入了我的查找:

嘿嘿嘿,我好鸡贼

说了我很鸡贼

看一下他的源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   private class RecyclerViewDataObserver extends AdapterDataObserver {
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
if (mAdapter.hasStableIds()) {
// TODO Determine what actually changed.
// This is more important to implement now since this callback will disable all
// animations because we cannot rely on positions.
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
} else {
mState.mStructureChanged = true;
setDataSetChangedAfterLayout();
}
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}

//省略部分代码...

void triggerUpdateProcessor() {
if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
}

这里简单的看一下第一行代码是检查recyclerview,如果有错就会抛异常。之后第一个if,不看了,默认是false,那么就看第二个条件内的代码。第一个是存储一个状态,第二个是我们要看到的东西了,点进去看下源码:

1
2
3
4
5
6
7
8
9
10
11
12
private void setDataSetChangedAfterLayout() {
//省略部分代码
mDataSetHasChangedAfterLayout = true;
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
}
}
mRecycler.setAdapterPositionsAsUnknown();
}

可以看到它会遍历ViewHolder,然后给holder添加flag: FLAG_ADAPTER_POSITION_UNKNOWN这个flag会让viewholder重新绑定到recyclerview上以确定自己的position,最后一个方法会让缓存的viewholder也打上上面提到的flag。

最后再回顾一下(如果有纰漏敬请指出,因为我这源码也没非常仔细的阅读):

  • 在想要更新RecyclerView的界面时,我们通常会先更新数据源(List之类的),然后调用Adapter的notifyDataSetChanged()方法

  • 在RecyclerView内部notifyDataSetChanged()方法调用了mObservable.notifyChanged();而mObservable是一个被观察者。

  • 在RecyclerView内部找到mObservable的真实类型,发现是RecyclerViewDataObserver,寻找notifyChanged()时会调用的onChanged()方法。

  • 发现onChanged()方法最终会给viewholder设置flag,让他们重新绑定到RecyclerView上,在重新绑定的过程中无疑是会在onBindViewHolder里重新设置数据的,而数据源我们已经更新过了,新的数据就会被显示到界面上,以上就是这整个流程了。

5、最后的一点思考

说实话最近在写东西的时候经常用回调,因为一些工具类或者dialog、window之类的,在自己自定义的时候通常需要回调把点击事件传出来,不然感觉传view设置点击什么的感觉也挺麻烦的,不如我里面逻辑处理好,就把点击事件传出来就好了。但是写到后面我这又是用的MVP,activity里各种回调满天飞,不过怎么说呢,我自己写的,我看起来逻辑还是很清晰的。如果是后面来人接手呢?虽然我注释写的都很清晰了,但是他在阅读代码的时候不得不深入我的工具类或者dialog window里去看我这个回调到底干了什么,所以这种方便自己麻烦别人的东西,我现在在想到底是算好的代码风格还是差的,有点糊涂。

好了,也挺久没发文了,而且干了七天,相信大家都很累了,休息休息了,祝各位有一个好的周末。

最后安利一下这个主播,小缘,安静听歌的感觉不错

参考资料:
《Android源码设计模式》