一直很好奇,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 )