一直很好奇,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 )
来源:https://www.cnblogs.com/nailperry/p/4668553.html