一直很好奇,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的实例化类。

 @Override
public void setAdapter(ListAdapter adapter) {
...
if (mAdapter != null) {
...
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); ... } else {
... } requestLayout();
}

查看AdapterDataSetObserver的onChanged方法:

 class AdapterDataSetObserver extends DataSetObserver
{
private Parcelable mInstanceState = null; AdapterDataSetObserver() {
}
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount(); if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
{
onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
}
//...省略不必要代码
}

在第20行,我们看见了requestLayout(),它就是用来重绘界面的,点击追踪requestLayout时,无法继续追踪,这时通过查找系统源码,我们发现AdapterDataSetObserver原来是抽象类AdapterView的内部类

public abstract class AdapterView<T extends Adapter> extends ViewGroup {
...
}
     class AdapterDataSetObserver extends DataSetObserver {

         private Parcelable mInstanceState = null;

         @Override
public void onChanged() {
mDataChanged = true;
mOldItemCount = mItemCount;
mItemCount = getAdapter().getCount(); // Detect the case where a cursor that was previously invalidated has
// been repopulated with new data.
if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
&& mOldItemCount == 0 && mItemCount > 0) {
AdapterView.this.onRestoreInstanceState(mInstanceState);
mInstanceState = null;
} else {
rememberSyncState();
}
checkFocus();
requestLayout();
} @Override
public void onInvalidated() {
mDataChanged = true; if (AdapterView.this.getAdapter().hasStableIds()) {
// Remember the current state for the case where our hosting activity is being
// stopped and later restarted
mInstanceState = AdapterView.this.onSaveInstanceState();
} // Data is invalid so we should reset our state
mOldItemCount = mItemCount;
mItemCount = 0;
mSelectedPosition = INVALID_POSITION;
mSelectedRowId = INVALID_ROW_ID;
mNextSelectedPosition = INVALID_POSITION;
mNextSelectedRowId = INVALID_ROW_ID;
mNeedSync = false; checkFocus();
requestLayout();
} public void clearSavedState() {
mInstanceState = null;
}
}

在21行,我们又看见了requestLayout(),Ctrl+单击该方法,进入到View类的同名方法

     /**
* Call this when something has changed which has invalidated the
* layout of this view. This will schedule a layout pass of the view
* tree.
*/
public void requestLayout() {
if (ViewDebug.TRACE_HIERARCHY) {
ViewDebug.trace(this, ViewDebug.HierarchyTraceType.REQUEST_LAYOUT);
} mPrivateFlags |= FORCE_LAYOUT;
mPrivateFlags |= INVALIDATED; if (mParent != null) {
if (mLayoutParams != null) {
mLayoutParams.resolveWithDirection(getResolvedLayoutDirection());
}
if (!mParent.isLayoutRequested()) {
mParent.requestLayout();
}
}
}

在第19行,我们发现该方法将requestLayout()任务上抛至其mParent,因此我们需要追踪mParent,先来看看谁为它赋值:

     /*
* Caller is responsible for calling requestLayout if necessary.
* (This allows addViewInLayout to not request a new layout.)
*/
void assignParent(ViewParent parent) {
if (mParent == null) {
mParent = parent;
} else if (parent == null) {
mParent = null;
} else {
throw new RuntimeException("view " + this + " being added, but"
+ " it already has a parent");
}
}

原来是assignParent,因此在构造子view的过程中,子view一定有assignParent的操作。根据View Tree的层级关系,我们可以猜测,这样一层层的上抛请求,最后应该上抛至Activity的根View,这个根View是谁?根据我们对Activity加载布局流程的理解,这个根View其实就是DecorView,那么我们先来看看DecorView中是否有requestLayout方法的具体实现。

我们知道DecorView是PhoneWindow的内部类,进入DecorView类,

 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方法。

 private void addView(View view, ViewGroup.LayoutParams params,
CompatibilityInfoHolder cih, boolean nest) {
... ViewRootImpl root;
... root = new ViewRootImpl(view.getContext());
...
root.setView(view, wparams, panelParentView);
}

在ViewRootImpl的setView方法中找到如下代码:view.assignParent(this);也即将DecorView的mParent指定为ViewRootImpl实例,并且在第6行发现调用了requestLayout方法。

      public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
mView = view;
...
          requestLayout();
          ... view.assignParent(this);
...
}

进入到ViewRootImpl的requestLayout方法:

     public void requestLayout() {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}

之后的流程参考从ViewRootImpl类分析View绘制的流程一文。

从以上分析可知,每一次notifyDataSetChange()都会引起界面的重绘,重绘的最终实现是在ViewRootImpl.java中。

二、notifyDataSetChanged与setAdapter区别

仔细阅读ListView的setAdapter方法,当ListView之前绑定过adapter信息时,在这里会清除原有Adapter和数据集观察者等信息,重置了ListView当前选中项等信息,并在方法的最后一句调用requestLayout进行界面的重绘。

 public void setAdapter(ListAdapter adapter) {
// 与原有观察者解绑定
if (mAdapter != null && mDataSetObserver != null) {
mAdapter.unregisterDataSetObserver(mDataSetObserver);
} resetList();
mRecycler.clear(); if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
} else {
mAdapter = adapter;
} mOldSelectedPosition = INVALID_POSITION;
mOldSelectedRowId = INVALID_ROW_ID; // AbsListView#setAdapter will update choice mode states.
super.setAdapter(adapter); if (mAdapter != null) {
mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
mOldItemCount = mItemCount;
mItemCount = mAdapter.getCount();
checkFocus();
// 重新绑定新的数据集观察者
mDataSetObserver = new AdapterDataSetObserver();
mAdapter.registerDataSetObserver(mDataSetObserver); mRecycler.setViewTypeCount(mAdapter.getViewTypeCount()); int position;
if (mStackFromBottom) {
position = lookForSelectablePosition(mItemCount - 1, false);
} else {
position = lookForSelectablePosition(0, true);
}
setSelectedPositionInt(position);
setNextSelectedPositionInt(position); if (mItemCount == 0) {
// Nothing selected
checkSelectionChanged();
}
} else {
mAreAllItemsSelectable = true;
checkFocus();
// Nothing selected
checkSelectionChanged();
}
// 重绘
requestLayout();
}

由此可知,调用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 )

Adapter.notifyDataSetChanged()源码分析以及与ListView.setAdapter的区别的更多相关文章

  1. JVM源码分析之MetaspaceSize和MaxMetaspaceSize的区别

    JVM加载类的时候,需要记录类的元数据,这些数据会保存在一个单独的内存区域内,在Java 7里,这个空间被称为永久代(Permgen),在Java 8里,使用元空间(Metaspace)代替了永久代. ...

  2. 源码分析二(ArrayList与LinkedList的区别)

    一:首先看一下ArrayList类的结构体系: public class ArrayList<E> extends AbstractList<E> implements Lis ...

  3. 源码分析四(HashMap与HashTable的区别 )

    这一节看一下HashMap与HashTable这两个类的区别,工作一段时间的程序员都知道, hashmap是非线程安全的,而且key值和value值允许为null,而hashtable是非线程安全的, ...

  4. 源码分析三(Vector与ArrayList的区别)

    前面讨论过ArrayList与LinkedList的区别,ArrayList的底层数据结构是数组Object[],而LinkedList底层维护 的是一个链表Entry,所以对于查询,肯定是Array ...

  5. BaseAdapter.notifyDataSetChanged()之观察者设计模式及源码分析

    BaseAdapter.notifyDataSetChanged()的实现涉及到设计模式-观察者模式,详情请参考我之前的博文设计模式之观察者模式 Ok,回到notifyDataSetChanged进行 ...

  6. Android base-adapter-helper 源码分析与扩展

    转载请标明出处:http://blog.csdn.net/lmj623565791/article/details/44014941,本文出自:[张鸿洋的博客] 本篇博客是我加入Android 开源项 ...

  7. RecyclerView 源码分析(二) —— 缓存机制

    在前一篇文章 RecyclerView 源码分析(一) -- 绘制流程解析 介绍了 RecyclerView 的绘制流程,RecyclerView 通过将绘制流程从 View 中抽取出来,放到 Lay ...

  8. Volley源码分析(2)----ImageLoader

    一:imageLoader 先来看看如何使用imageloader: public void showImg(View view){ ImageView imageView = (ImageView) ...

  9. documentsUI源码分析

    documentsUI源码分析 本文基于Android 6.0的源码,来分析documentsUI模块. 原本基于7.1源码看了两天,但是Android 7.1与6.0中documentsUI模块差异 ...

随机推荐

  1. python---基础知识回顾(十一)图像处理模块PIL

    前戏: 虽然PIL没有入OpenCV那样强大的功能,但是所提供的功能,在一般的图像处理中足够使用. 图像类别: 计算机绘图中有两类图像:一类是矢量图,另一类是点阵图(位图) 矢量图:基于计算机数字对象 ...

  2. Table of Contents

    程序设计 Java JavaSE Apache Commons Servlet & JSP Maven JMS ActiveMQ WebService CXF Jersey HttpClien ...

  3. 51nod1450 闯关游戏

    题目来源: TopCoder 基准时间限制:1 秒 空间限制:131072 KB 分值: 320 一个游戏App由N个小游戏(关卡)构成,将其标记为0,1,2,..N-1.这些小游戏没有相互制约的性质 ...

  4. 20155307 2016-2017-2 《Java程序设计》第6周学习总结

    20155307 2016-2017-2 <Java程序设计>第6周学习总结 教材学习内容总结 串流数据有来源及目的地,衔接两者的是串流对象.如果要将数据从来源取出,可以使用输入串流:如果 ...

  5. HDU 4500 小Q系列故事——屌丝的逆袭

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4500 解题报告:简单题,数据范围不大,直接暴力每个点,然后再比较出得分最大的点的位置和分数. #inc ...

  6. C语言编写守护进程

    概念 守护进程(daemon)是一种运行在后台的一种特殊的进程,它独立于控制终端并且周期性的执行某种任务或等待处理某些发生的事件.由于在Linux中,每个系统与用户进行交流的界面成为终端,每一个从此终 ...

  7. POJ 1986 Distance Queries (Tarjan算法求最近公共祖先)

    题目链接 Description Farmer John's cows refused to run in his marathon since he chose a path much too lo ...

  8. NYOJ 116 士兵杀敌(二) (树状数组)

    题目链接 描述 南将军手下有N个士兵,分别编号1到N,这些士兵的杀敌数都是已知的.小工是南将军手下的军师,南将军经常想知道第m号到第n号士兵的总杀敌数,请你帮助小工来回答南将军吧.南将军的某次询问之后 ...

  9. CMD命令利用tasklist与taskkill关闭程序

    昨天远程服务器后,服务器无故卡住了,鼠标各种延迟与无反应,想在进程管理器里关闭程序也卡住,想点击重启系统也卡死无反应.纠结后win+R打开了cmd用shutdown重启才算搞定.重启期间思考了下,如何 ...

  10. Struts2笔记3--获取ServletAPI和OGNL与值栈

    获取ServletAPI: 第一种方式: //在request域中放入属性req,暂且认为getContext()获取的是request域空间,但实际不是 ActionContext.getConte ...