寫在前面
以前聽說過一句話,說是** 自己寫過程式碼總量沒有超過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)就是人。首先我們用以上我說的,那玩意就是回撥,用回撥來寫一個看看。那我們先寫一個被觀察者“水壺”來看看:
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我是咋寫的:
public interface Observer<T> {
void receiverEvent(T t);
}
複製程式碼
很簡單的一個介面,寫上泛型,嗯,順便練習一下泛型...只有一個方法,用來回撥。有介面那我們肯定要有實現類,我這個場景裡說了,人是觀察者,於是我寫了一個Observer的實現類:
public abstract class People implements Observer<String> {
@Override
public void receiverEvent(String s) {
System.out.println(s);
dealWithEvent();
}
/**
* 交給使用者去處理事件
*/
public abstract void dealWithEvent();
}
複製程式碼
我把這個People設計為一個抽象類,這樣我可以在接收到這個事件的時候做一些簡單的處理(把他列印出來……),然後再把具體的邏輯交給這個抽象類的子類來做,我這邏輯比較簡單,就沒傳什麼引數進去了。最後免不了跑起來看看了~
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:
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看起:
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:
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()最終會呼叫他自身帶參的過載方法,看下程式碼:
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()方法
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
複製程式碼
繼續追蹤這個原始碼,看看咋回事
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()的原始碼,看看到底是誰的方法。
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輸入了我的查詢:
看一下他的原始碼:
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,那麼就看第二個條件內的程式碼。第一個是儲存一個狀態,第二個是我們要看到的東西了,點進去看下原始碼:
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原始碼設計模式》