一直很好奇,notifyDataSetChanged究竟是重繪了整個ListView還是只重繪了被修改的那些Item,它與重新設置適配器即調用setAdapter的區別在哪里?所以特地追蹤了一下源碼,過程如下:
一、notifyDataSetChanged實現機制
自定義Activity中有如下調用語句:
checkoutAdapter.notifyDataSetChanged();
點擊notifyDataSetChanged()進行代碼跟蹤。首先,進入到BaseAdapter的notifyDataSetChanged方法:
public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); }
我們發現其實就是DataSetObservable這個對象在發生作用,點擊notifyChanged進行追蹤。
public class DataSetObservable extends Observable<DataSetObserver> { /** * Invokes onChanged on each observer. Called when the data set being observed has * changed, and which when read contains the new state of the data. */ 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(); } } }
繼續跟蹤onChanged(),我們發現DataSetObserver 是個抽象類,其派生類實例對象是在哪里指定的呢?根據經驗,我們需要回溯至adapter的構造過程。
public abstract class DataSetObserver { /** * This method is called when the entire data set has changed, * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}. */ public void onChanged() { // Do nothing } /** * This method is called when the entire data becomes invalid, * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a * {@link Cursor}. */ public void onInvalidated() { // Do nothing } }
先看adapter的構造函數
CheckOut_DishListViewAdapter checkoutAdapter;
// 綁定適配器 checkoutAdapter = new CheckOut_DishListViewAdapter( CheckOutActivity.this, list_dish); list_view_dish.setAdapter(checkoutAdapter);
public class CheckOut_DishListViewAdapter extends BaseAdapter { private DecimalFormat df = new DecimalFormat("######0.00");// 用於double保留兩位小數 private LayoutInflater mInflater; private List<HashMap<String, Object>> list; public CheckOut_DishListViewAdapter(Context con, List<HashMap<String, Object>> list) { mInflater = LayoutInflater.from(con); this.list = list; }
顯然沒有DataSetObserver的有關信息。
再看ListView中的setAdapter方法,我們省略其他代碼,只看與DataSetObserver相關的部分,從mDataSetObserver = new AdapterDataSetObserver();可知,AdapterDataSetObserver是DataSetObserver的實例化類。
1 @Override 2 public void setAdapter(ListAdapter adapter) { 3 ... 22 if (mAdapter != null) { 23 ...27 28 mDataSetObserver = new AdapterDataSetObserver(); 29 mAdapter.registerDataSetObserver(mDataSetObserver); 30 31 ...46 } else { 47 ...51 } 52 53 requestLayout(); 54 }
查看AdapterDataSetObserver的onChanged方法:
1 class AdapterDataSetObserver extends DataSetObserver 2 { 3 private Parcelable mInstanceState = null; 4 5 AdapterDataSetObserver() { 6 } 7 public void onChanged() { 8 mDataChanged = true; 9 mOldItemCount = mItemCount; 10 mItemCount = getAdapter().getCount(); 11 12 if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0)) 13 { 14 onRestoreInstanceState(mInstanceState); 15 mInstanceState = null; 16 } else { 17 rememberSyncState(); 18 } 19 checkFocus(); 20 requestLayout(); 21 } 22 //...省略不必要代碼 23 }
在第20行,我們看見了requestLayout(),它就是用來重繪界面的,點擊追蹤requestLayout時,無法繼續追蹤,這時通過查找系統源碼,我們發現AdapterDataSetObserver原來是抽象類AdapterView的內部類
public abstract class AdapterView<T extends Adapter> extends ViewGroup { ... }
1 class AdapterDataSetObserver extends DataSetObserver { 2 3 private Parcelable mInstanceState = null; 4 5 @Override 6 public void onChanged() { 7 mDataChanged = true; 8 mOldItemCount = mItemCount; 9 mItemCount = getAdapter().getCount(); 10 11 // Detect the case where a cursor that was previously invalidated has 12 // been repopulated with new data. 13 if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null 14 && mOldItemCount == 0 && mItemCount > 0) { 15 AdapterView.this.onRestoreInstanceState(mInstanceState); 16 mInstanceState = null; 17 } else { 18 rememberSyncState(); 19 } 20 checkFocus(); 21 requestLayout(); 22 } 23 24 @Override 25 public void onInvalidated() { 26 mDataChanged = true; 27 28 if (AdapterView.this.getAdapter().hasStableIds()) { 29 // Remember the current state for the case where our hosting activity is being 30 // stopped and later restarted 31 mInstanceState = AdapterView.this.onSaveInstanceState(); 32 } 33 34 // Data is invalid so we should reset our state 35 mOldItemCount = mItemCount; 36 mItemCount = 0; 37 mSelectedPosition = INVALID_POSITION; 38 mSelectedRowId = INVALID_ROW_ID; 39 mNextSelectedPosition = INVALID_POSITION; 40 mNextSelectedRowId = INVALID_ROW_ID; 41 mNeedSync = false; 42 43 checkFocus(); 44 requestLayout(); 45 } 46 47 public void clearSavedState() { 48 mInstanceState = null; 49 } 50 }
在21行,我們又看見了requestLayout(),Ctrl+單擊該方法,進入到View類的同名方法
1 /** 2 * Call this when something has changed which has invalidated the 3 * layout of this view. This will schedule a layout pass of the view 4 * tree. 5 */ 6 public void requestLayout() { 7 if (ViewDebug.TRACE_HIERARCHY) { 8 ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT); 9 } 10 11 mPrivateFlags |= FORCE_LAYOUT; 12 mPrivateFlags |= INVALIDATED; 13 14 if (mParent != null) { 15 if (mLayoutParams != null) { 16 mLayoutParams.resolveWithDirection(getResolvedLayoutDirection()); 17 } 18 if (!mParent.isLayoutRequested()) { 19 mParent.requestLayout(); 20 } 21 } 22 }
在第19行,我們發現該方法將requestLayout()任務上拋至其mParent,因此我們需要追蹤mParent,先來看看誰為它賦值:
1 /* 2 * Caller is responsible for calling requestLayout if necessary. 3 * (This allows addViewInLayout to not request a new layout.) 4 */ 5 void assignParent(ViewParent parent) { 6 if (mParent == null) { 7 mParent = parent; 8 } else if (parent == null) { 9 mParent = null; 10 } else { 11 throw new RuntimeException("view " + this + " being added, but" 12 + " it already has a parent"); 13 } 14 }
原來是assignParent,因此在構造子view的過程中,子view一定有assignParent的操作。根據View Tree的層級關系,我們可以猜測,這樣一層層的上拋請求,最后應該上拋至Activity的根View,這個根View是誰?根據我們對Activity加載布局流程的理解,這個根View其實就是DecorView,那么我們先來看看DecorView中是否有requestLayout方法的具體實現。
我們知道DecorView是PhoneWindow的內部類,進入DecorView類,
1 private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
發現DecorView繼承自FrameLayout ,也即間接繼承自View,但DecorView中並未重寫requestLayout方法,說明DecorView並不是requestLayout的最終執行者,DecorView存在mParent,要想弄清楚DecorView的mParent是誰,我們有必要回顧一下DecorView是如何裝載到Activity的。
我們按照流程圖一級一級的找,在WindowManagerImpl中找到addView方法,發現新建了一個ViewRootImpl對象,並在最后調用ViewRootImpl的setView方法,接下來我們繼續跟進setView方法。
1 private void addView(View view, ViewGroup.LayoutParams params, 2 CompatibilityInfoHolder cih, boolean nest) { 3 ... 4 5 ViewRootImpl root; 6 ... 7 8 root = new ViewRootImpl(view.getContext()); 9 ... 10 root.setView(view, wparams, panelParentView); 11 }
在ViewRootImpl的setView方法中找到如下代碼:view.assignParent(this);也即將DecorView的mParent指定為ViewRootImpl實例,並且在第6行發現調用了requestLayout方法。
1 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { 2 synchronized (this) { 3 if (mView == null) { 4 mView = view; 5 ... 6 requestLayout(); 7 ... 8 9 view.assignParent(this); 10 ... 11 }
進入到ViewRootImpl的requestLayout方法:
1 public void requestLayout() { 2 checkThread(); 3 mLayoutRequested = true; 4 scheduleTraversals(); 5 }
之后的流程參考從ViewRootImpl類分析View繪制的流程一文。
從以上分析可知,每一次notifyDataSetChange()都會引起界面的重繪,重繪的最終實現是在ViewRootImpl.java中。
二、notifyDataSetChanged與setAdapter區別
仔細閱讀ListView的setAdapter方法,當ListView之前綁定過adapter信息時,在這里會清除原有Adapter和數據集觀察者等信息,重置了ListView當前選中項等信息,並在方法的最后一句調用requestLayout進行界面的重繪。
1 public void setAdapter(ListAdapter adapter) { 2 // 與原有觀察者解綁定 3 if (mAdapter != null && mDataSetObserver != null) { 4 mAdapter.unregisterDataSetObserver(mDataSetObserver); 5 } 6 7 resetList(); 8 mRecycler.clear(); 9 10 if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) { 11 mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter); 12 } else { 13 mAdapter = adapter; 14 } 15 16 mOldSelectedPosition = INVALID_POSITION; 17 mOldSelectedRowId = INVALID_ROW_ID; 18 19 // AbsListView#setAdapter will update choice mode states. 20 super.setAdapter(adapter); 21 22 if (mAdapter != null) { 23 mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); 24 mOldItemCount = mItemCount; 25 mItemCount = mAdapter.getCount(); 26 checkFocus(); 27 // 重新綁定新的數據集觀察者 28 mDataSetObserver = new AdapterDataSetObserver(); 29 mAdapter.registerDataSetObserver(mDataSetObserver); 30 31 mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); 32 33 int position; 34 if (mStackFromBottom) { 35 position = lookForSelectablePosition(mItemCount - 1, false); 36 } else { 37 position = lookForSelectablePosition(0, true); 38 } 39 setSelectedPositionInt(position); 40 setNextSelectedPositionInt(position); 41 42 if (mItemCount == 0) { 43 // Nothing selected 44 checkSelectionChanged(); 45 } 46 } else { 47 mAreAllItemsSelectable = true; 48 checkFocus(); 49 // Nothing selected 50 checkSelectionChanged(); 51 } 52 // 重繪 53 requestLayout(); 54 }
由此可知,調用adapter.notifyDataSetChanged與listView.setAdapter函數都會引起界面重繪,區別是前者會保留原有位置、數據信息,后者是回到初始狀態。
注:以上過程純屬個人探索,如有錯誤敬請批評指正。
參考文獻:
1.從ViewRootImpl類分析View繪制的流程(http://blog.csdn.net/feiduclear_up/article/details/46772477)
2.從源代碼的角度分析--在BaseAdapter調用notifyDataSetChanged()之后發生了什么(http://www.cnblogs.com/kissazi2/p/3721941.html )