一直很好奇,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. P4310 绝世好题

    P4310 绝世好题 题目描述 给定一个长度为n的数列ai,求ai的子序列bi的最长长度,满足bi&bi-1!=0(2<=i<=len). 说明 对于100%的数据,1<=n ...

  2. Oracle的基本语法,存储函数及触发器

    1.PL/SQL    PL/SQL是Oracle对 ql语言的过程化扩展,指在 SQL 命令语言中增加了过程处理语句,使SQL语言具有过程处理能力.把 SQL 语言的数据操纵能  力与过程语言的数据 ...

  3. Nginx location 配置用法及正则例子

    Nginx location 配置语法     1. location [ = | ~ | ~* | ^~ ] uri { ... }     2. location @name { ... }   ...

  4. bzoj千题计划153:bzoj2431: [HAOI2009]逆序对数列

    http://www.lydsy.com/JudgeOnline/problem.php?id=2431 dp[i][j] 表示i的排列,有j个逆序对的方案数 加入i+1,此时i+1是排列中最大的数, ...

  5. xpath定位中详解id 、starts-with、contains、text()和last() 的用法

    1.XPATH使用方法 使用XPATH有如下几种方法定位元素(相比CSS选择器,方法稍微多一点): a.通过绝对路径定位元素(不推荐!) WebElement ele = driver.findEle ...

  6. Java并发编程原理与实战八:产生线程安全性问题原因(javap字节码分析)

    前面我们说到多线程带来的风险,其中一个很重要的就是安全性,因为其重要性因此,放到本章来进行讲解,那么线程安全性问题产生的原因,我们这节将从底层字节码来进行分析. 一.问题引出 先看一段代码 packa ...

  7. 自己写的一个小的剪刀——石头——布游戏的GUI程序

    很简单的一个程序,建议各位初学Java的同学可以试试写写这个程序: import javax.swing.JOptionPane; public class Game { public static ...

  8. Asp.net 中,在服务端向客户端写脚本的常用方法

    在Asp.net 服务端处理脚本,一般都用 ClientScriptManager ,即web窗体服务端的this.ClientScript.该对象比较常用的方法: 1.RegisterArrayDe ...

  9. LintCode 388: Kth Permutation

    LintCode 388: Kth Permutation 题目描述 给定 n 和 k,求123..n组成的排列中的第 k 个排列. 样例 对于 n = 3, 所有的排列如下: 123 132 213 ...

  10. 连接mysql提示Establishing SSL connection without server's identity verification is not recommended错误

    Establishing SSL connection without server's identity verification is not recommended. According to ...