zoukankan      html  css  js  c++  java
  • 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 )

  • 相关阅读:
    DNX SDK版本 “dnx-clr-win-x86.1.0.0-beta5”无法安装
    【循序渐进MVC】第一回——一物多用Project.json依赖关系之dependencies节点
    ASP.NET页面中去除VIEWSTATE视图状态乱码
    git入门操作命令(转载)
    论火车票订单系统中并发问题和锁机制的探讨(转载)
    如何取消MSSQL自带智能提示步骤,使用第三方智能提示插件
    查看Windows服务器登录日志
    浅谈分布式计算系统和集群系统的区别
    数据库设计优化经验谈(转载)
    程序性能优化之防止装箱将拆装箱最小化
  • 原文地址:https://www.cnblogs.com/nailperry/p/4668553.html
Copyright © 2011-2022 走看看