Adapter.notifyDataSetChanged()源码分析以及与ListView.setAdapter的区别

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

posted @ 2015-07-22 20:31  nailperry  阅读(9970)  评论(0编辑  收藏  举报