*本篇文章已授權微信公衆號 guolin_blog (郭霖)獨家發佈
雖然現在RecyclerView很好用,也在逐漸替代ListView。Github 上也很多實用的封裝。但是這些都不阻礙我們學習ListView優秀的源碼設計。
進入正題,我用的是Api-23的源碼。接下來就從源碼的角度帶你學習ListView中的觀察者模式
當我們開啓異步線程,向服務端拉取數據後,數據源已經更新了,此時想要更新ListView的視圖以顯示新的數據。
ListView使用了Adapter模式,很簡單隻需一行代碼就能完成ListView的更新。
mAdapter.notifyDataSetChanged();
那麼這裏引出一個問題,
更新ListView的工作,是Adapter完成的還是ListView自身內部完成的?可以先猜想一下再往下看。
因爲我之前已經學習過自定義控件,所以我看源碼之前猜想是ListView完成的。慣性使然,我想到他可能是調用了onLayout(),onDraw()等方法呀,去重新佈局,繪製
那接下來就解開疑惑吧。
先找到源頭,從ListView綁定Adapter那裏開始。
mListView.setAdapter(mAdapter);
ListView和Adapter就是用這行代碼建立起關聯的。
那麼跟蹤setAdapter方法進去:
public void setAdapter(ListAdapter adapter) {
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
resetList();
mRecycler.clear();
if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
}
mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID;
// AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter);
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position);
if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
requestLayout();
}
方法是這樣開始的
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
}
先判斷mAdapter != null && mDataSetObserver != null
mAdapter肯定是不爲null的,那麼mDataSetObserver呢?這個引用是哪裏被賦值的,先不管,繼續往下看setAdapter方法。
這裏先分享我看源碼的方法吧:
剛開始的時候我是很喜歡往深處闖,導致看了一天都無法自拔,思路又散了。現在我看源碼都是挑重點看,比如這個setAdapter方法,一路看下來都沒有return 語句跳出,那麼就一定會來到if(mAdapter !=null )這個判斷,如下:
if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver);
//代碼省略
}
到了這裏,我們也就找到了mDataSetObserver,原來是在這裏被賦值的。
現在得出小結論:
1.在ListView的setAdapter方法中,生成了一個AdapterDataSetObserver對象並賦值給mDataSetObserver
2.調用Adapter的registerDataSetObserver方法將mDataSetObserver註冊進去。
現在我們好奇的是Adapter的registerDataSetObserver方法。繼續前進。
在BaseAdapter類中找到了registerDataSetObserver方法,並且也找到了經常調用的,很熟悉的notifyDataSetChanged方法。如下:
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public boolean hasStableIds() {
return false;
}
public void registerDataSetObserver(DataSetObserver observer) {
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
mDataSetObservable.unregisterObserver(observer);
}
/**
* Notifies the attached observers that the underlying data has been changed
* and any View reflecting the data set should refresh itself.
*/
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
/**
* Notifies the attached observers that the underlying data is no longer valid
* or available. Once invoked this adapter is no longer valid and should
* not report further data set changes.
*/
public void notifyDataSetInvalidated() {
mDataSetObservable.notifyInvalidated();
}
//代碼省略
}
可以看到,在registerDataSetObserver方法中,又調用了DataSetObservable的registerObserver方法將傳進來的AdapterDataSetObserver對象註冊進去,那麼這個DataSetObservable又是什麼呢?繼續跟進
這個DataSetObservable源碼比較少,那就全部貼出
public class DataSetObservable extends Observable<DataSetObserver> {
/**
* Invokes {@link DataSetObserver#onChanged} on each observer.
* Called when the contents of the data set have changed. The recipient
* will obtain the new contents the next time it queries the data set.
*/
public void notifyChanged() {
synchronized(mObservers) {
// 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();
}
}
}
/**
* Invokes {@link DataSetObserver#onInvalidated} on each observer.
* Called when the data set is no longer valid and cannot be queried again,
* such as when the data set has been closed.
*/
public void notifyInvalidated() {
synchronized (mObservers) {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onInvalidated();
}
}
}
}
好像看不太懂。mObservers是什麼?竟然沒有registerObserver方法。哈哈,那肯定是父類繼承下來的啊。在DataSetObservable類中暫時沒我們想要知道的信息,那麼就看看他的父類Observable吧。Observable還是個泛型。不管,看內部實現原理就好
public abstract class Observable<T> {
/**
* The list of observers. An observer can be in the list at most
* once and will never be null.
*/
protected final ArrayList<T> mObservers = new ArrayList<T>();
/**
* Adds an observer to the list. The observer cannot be null and it must not already
* be registered.
* @param observer the observer to register
* @throws IllegalArgumentException the observer is null
* @throws IllegalStateException the observer is already registered
*/
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
//代碼省略
}
找到了registerObserver方法。代碼邏輯還挺簡單的。
我們又可以得出小結論:
DataSetObservable的內部維護着一個觀察者集合,即源碼中的mObservers。當我們的ListView綁定了Adapter,調用BaseAdapter的registerDataSetObserver方法時,實際上是在這個觀察者集合mObservers裏將該觀察者添加進來。對ListView來說,這個觀察者就是AdapterDataSetObserver
完成註冊。以上就是setAdapter方法的源碼分析
再看到BaseAdapter的notifyDataSetChanged()方法
public void notifyDataSetChanged() {
mDataSetObservable.notifyChanged();
}
內部調用了DataSetObservable的notifyChanged方法
再回到DataSetObservable的源碼,看到notifyChanged()方法
public void notifyChanged() {
synchronized(mObservers) {
// 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();
}
}
}
從觀察者集合裏遍歷出觀察者,並調用該觀察者的onChange()方法
很清楚了吧。
當我們調用Adapter的notifyDataSetChanged方法更新ListView。
在notifyDataSetChanged方法中又會調用DataSetObservable的notifyChanged方法。
而從DataSetObservable的源碼中,我們知道了在notifyChanged方法中又會遍歷出
AdapterDataSetObserver(觀察者),並調用這個觀察者的onChanged()方法。
完畢,底層實現就是這樣。
接下來只需要知道AdapterDataSetObserver(觀察者)的onChanged()方法裏做了什麼就好了。
而AdapterDataSetObserver,是ListView的父類AdapterView的一個內部類。他是真的有onChanged方法的。不信你看
class AdapterDataSetObserver extends DataSetObserver {
private Parcelable mInstanceState = null;
@Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount();
// Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
//代碼省略
}
終於揭開謎底,在AdapterDataSetObserver的onChanged()方法裏,實際上是調用了View的requestLayout()方法進行重新策略,佈局,繪製整個ListView的子項item view
requestLayout()的源碼如下:
public void requestLayout() {
if (mMeasureCache != null) mMeasureCache.clear();
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
// Only trigger request-during-layout logic if this is the view requesting it,
// not the views in its parent hierarchy
ViewRootImpl viewRoot = getViewRootImpl();
if (viewRoot != null && viewRoot.isInLayout()) {
if (!viewRoot.requestLayoutDuringLayout(this)) {
return;
}
}
mAttachInfo.mViewRequestingLayout = this;
}
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
mAttachInfo.mViewRequestingLayout = null;
}
}
AdapterView是繼承ViewGroup的,但是ViewGroup並沒有重寫requestLayout()方法。有能力的同學可以繼續深入研究AdapterView到底是怎麼重新佈局的
至此,我們已經解開了開篇的疑惑
綜上所述,AdapterDataSetObserver這個是觀察者,在AdapterDataSetObserver的onChanged函數中,實際上調用的是View中的方法完成了整個更新ListView的工作,AdapterDataSetObserver只是在外層進行了包裝,真正的核心功能是ListView,更加準確的說話是ListView的父類AdapterView。
可以換個角度理解,ListView 調用 setAdapter 方法,將一個觀察者注入 Adapter 中維護的觀察者序列,這樣 ListView 和 Adapter 就建立起了關聯,當有了數據變化,我們調用了 Adapter 的 notifyDataSetChanged 方法,內部會遍歷出觀察者,ListView是觀察者,Adapter 是被觀察者。ListView對Adapter訂閱。有了數據更新後,Adapter通知ListView,告訴 ListView ,你可以更新視圖了,之後 ListView調用 requestLayout 方法重新繪製界面,完成視圖的更新。
ListView就是通過Adapter模式,觀察者模式,子項複用機制實現了視圖良好的擴展性,節約了內存開銷,提高了運行效率