一直很好奇,notifyDataSetChanged究竟是重绘了整个ListView还是只重绘了被修改的那些Item,它与重新设置适配器即调用setAdapter的区别在哪里?所以特地追踪了一下源码,过程如下:

一、notifyDataSetChanged实现机制

自定义Activity中有如下调用语句:

  1. checkoutAdapter.notifyDataSetChanged();

点击notifyDataSetChanged()进行代码跟踪。首先,进入到BaseAdapter的notifyDataSetChanged方法:

  1. public void notifyDataSetChanged() {
  2. mDataSetObservable.notifyChanged();
  3. }

我们发现其实就是DataSetObservable这个对象在发生作用,点击notifyChanged进行追踪。

  1. public class DataSetObservable extends Observable<DataSetObserver> {
  2. /**
  3. * Invokes onChanged on each observer. Called when the data set being observed has
  4. * changed, and which when read contains the new state of the data.
  5. */
  6. public void notifyChanged() {
  7. synchronized(mObservers) {
  8. // since onChanged() is implemented by the app, it could do anything, including
  9. // removing itself from {@link mObservers} - and that could cause problems if
  10. // an iterator is used on the ArrayList {@link mObservers}.
  11. // to avoid such problems, just march thru the list in the reverse order.
  12. for (int i = mObservers.size() - 1; i >= 0; i--) {
  13. mObservers.get(i).onChanged();
  14. }
  15. }
  16. }

继续跟踪onChanged(),我们发现DataSetObserver 是个抽象类,其派生类实例对象是在哪里指定的呢?根据经验,我们需要回溯至adapter的构造过程。

  1. public abstract class DataSetObserver {
  2. /**
  3. * This method is called when the entire data set has changed,
  4. * most likely through a call to {@link Cursor#requery()} on a {@link Cursor}.
  5. */
  6. public void onChanged() {
  7. // Do nothing
  8. }
  9.  
  10. /**
  11. * This method is called when the entire data becomes invalid,
  12. * most likely through a call to {@link Cursor#deactivate()} or {@link Cursor#close()} on a
  13. * {@link Cursor}.
  14. */
  15. public void onInvalidated() {
  16. // Do nothing
  17. }
  18. }

先看adapter的构造函数

  1. CheckOut_DishListViewAdapter checkoutAdapter;
    // 绑定适配器
  2. checkoutAdapter = new CheckOut_DishListViewAdapter(
  3. CheckOutActivity.this, list_dish);
  4. list_view_dish.setAdapter(checkoutAdapter);
  1. public class CheckOut_DishListViewAdapter extends BaseAdapter {
  2. private DecimalFormat df = new DecimalFormat("######0.00");// 用于double保留两位小数
  3. private LayoutInflater mInflater;
  4. private List<HashMap<String, Object>> list;
  5.  
  6. public CheckOut_DishListViewAdapter(Context con,
  7. List<HashMap<String, Object>> list) {
  8. mInflater = LayoutInflater.from(con);
  9. this.list = list;
  10. }

显然没有DataSetObserver的有关信息。

再看ListView中的setAdapter方法,我们省略其他代码,只看与DataSetObserver相关的部分,从mDataSetObserver = new AdapterDataSetObserver();可知,AdapterDataSetObserver是DataSetObserver的实例化类。

  1. @Override
  2. public void setAdapter(ListAdapter adapter) {
  3. ...
  4. if (mAdapter != null) {
  5. ...
  6. mDataSetObserver = new AdapterDataSetObserver();
  7. mAdapter.registerDataSetObserver(mDataSetObserver);
  8.  
  9. ... } else {
  10. ... }
  11.  
  12. requestLayout();
  13. }

查看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的内部类

  1. public abstract class AdapterView<T extends Adapter> extends ViewGroup {
  2. ...
  3. }
  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 )

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. Git4:Git标签

    目录 简介 新建标签 查看标签详细信息 切换标签 后期添加标签 将标签推送到远端仓库 简介 Git可以对某一时间点上的版本打上标签.人们在发布某个软件版本(比如 v1.0 等等)的时候,经常这么做.本 ...

  2. bzoj千题计划146:bzoj3295: [Cqoi2011]动态逆序对

    http://www.lydsy.com/JudgeOnline/problem.php?id=3295 正着删除看做倒着添加 对答案有贡献的数对满足以下3个条件: 出现时间:i<=j 权值大小 ...

  3. 洛谷 P3382 【模板】三分法

    https://www.luogu.org/problem/show?pid=3382 题目描述 如题,给出一个N次函数,保证在范围[l,r]内存在一点x,使得[l,x]上单调增,[x,r]上单调减. ...

  4. Linux6.x修改出eth0网卡的解决方法

    1. 编辑70-persistent-net配置文件: # -persistent-net.rules 如果没有就新建一个,添加如下内容: # PCI device 0x14e4:0x165f (tg ...

  5. Linux下编译Phantomjs

    1.安装依赖的库 <pre> sudo apt-get install g++ flex bison gperf ruby perl \ libsqlite3-dev libfontcon ...

  6. [php]php总结(1)

    1.变量可以连续传递赋值2.var_dump()打印变量信息3.isset()与unset()4.可变变量$p = "temp";$$p则表示$temp变量,即最右边的变量的值为下 ...

  7. [大数据测试]ETL测试或数据仓库测试入门

    转载自: http://blog.csdn.net/zhusongziye/article/details/78633934 概述 在我们学习ETL测试之前,先了解下business intellig ...

  8. Windows上安装QT4后更改MinGW的路径

    在windows上安装使用MinGW的QT4时,并不会一起安装MinGW. 在安装过程中,会让你指定已经安装的MinGW的路径. 当你使用QT4时,将使用你指定的MinGW的路径下的g++来编译构建程 ...

  9. [OI]省选前模板整理

    省选前把板子整理一遍,如果发现有脑抽写错的情况,欢迎各位神犇打脸 :) 数学知识 数论: //组合数 //C(n,m) 在n个数中选m个的方案数 ll C[N][N]; void get_C(int ...

  10. beta版1.1.2

    此次的beta版本做的修改重点在内部的算法上面. 因为之前所做的判断不重复的随机数方面采用的是String.valueof()的方式,即将int类型数字转换成string类型,比较string中是否出 ...