一直很好奇,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. java.lang.NoClassDefFoundError: org/hibernate/service/ServiceRegistry] 类似问题

    使用Hibernate时出现以上错误,在Java Project中运行无误,但是来到Dynamic Web Project中却出现了如下错误: hibernate 报错:java.lang.NoCla ...

  2. Spring @Transactional注解不回滚不起作用无效

    原文地址:http://www.cnblogs.com/wuxiaofeng/p/6819209.html   这几天在项目里面发现我使用@Transactional之后,抛了异常居然不回滚.后来终于 ...

  3. MySQL完整复制表到另一个新表

    1. 复制表结构 CREATE TABLE newuser LIKE user; 2. 导入数据 INSERT INTO newauser SELECT * FROM user;

  4. RulersGuides.js – 网站中实现 Photoshop 标尺效果

    RulersGuides.js 是一个 JavaScript 库,在网页上添加类似 Photoshop 中的标尺和辅助网格线.要创建创建网格线线,请单击垂直或水平的标尺,然后推动就可以.还可以使用相应 ...

  5. 1.phpcms 伪静态

    location / { if (!-f $request_filename){ rewrite (.*) /index.php; } rewrite ^/caipu-([-]+)-([-]+)-([ ...

  6. 深度优先搜索(DFS)----------------Tju_Oj_3517The longest athletic track

    这个题主要考察对树的操作,主要思想是DFS或者BFS,其次是找树的直径方法(既要运用两次BFS/DFS),最后作为小白,还练习了vector的操作. DFS框架伪码: bool DSF(Node on ...

  7. Let Me Count The Ways(Kickstart Round H 2018)

    题目链接:https://code.google.com/codejam/contest/3324486/dashboard#s=p2 题目: 思路: 代码实现如下: #include <set ...

  8. Oracle嵌套表

    一.介绍  1.定义 嵌套表是表中之表.一个嵌套表是某些行的集合,它在主表中表示为其中的一列.对主表中的每一条记录,嵌套表可以包含多个行.在某种意义上,它是在一个表中存储一对多关系的一种方法.    ...

  9. react 带参数事件方法不立即执行

    handleClick:()=>{this.to_step('to_step3_2')}}

  10. 兴人类TDD培训札记

    兴人类TDD培训札记 恰同学少年,风华正茂:书生意气,挥斥方遒 -- <沁园春 长沙> 幸之 前不久,非常幸运地全程参与了公司与南京5所知名高校合作的"兴人类TDD培训" ...