从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么

导师安排我做一个小项目,其中涉及到利用Adapter作为ListView的适配器,为ListView提供数据。选中某一项后,要让这一项变成选中状态,也就是背景图片要换一下。下面我就用一个小例子来模拟。重点不在于实现,而是了解Adapter中notifyDataSetChanged()背后的运行机制。

    我们先做一个小Demo(文中涉及的Demo在文章末尾),功能是选中某一项后,背景颜色会变红。代码非常简单,这里就不解释了。值得注意的是,当我们需要ListView进行刷新的时候,我们需要调用Adapter.notifyDataSetChanged()来让界面刷新。

 1 public class MainActivity extends Activity {
 2     @Override
 3     protected void onCreate(Bundle savedInstanceState) {
 4
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7
 8     ListView main_list = (ListView)this.findViewById(R.id.main_list);
 9     MyArrayAdapter mArrayList=new MyArrayAdapter(this,R.layout.list_item,getData());
10     main_list.setAdapter(mArrayList);
11     main_list.setOnItemClickListener(mArrayList);
12     }
13
14     private String[] getData() {
15         return new String[]{"测试数据1","测试数据2","测试数据3","测试数据4"};
16     }
17 }

适配器MyArrayAdapter代码:

 1 public class MyArrayAdapter extends ArrayAdapter<String> implements
 2         OnItemClickListener {
 3
 4     private int itemClicked;
 5
 6     public MyArrayAdapter(Context context, int textViewResourceId,
 7             String[] objects) {
 8         super(context,  textViewResourceId, objects);
 9
10     }
11 @Override
12 public View getView(int position, View convertView, ViewGroup parent) {
13
14     convertView=super.getView(position, convertView, parent);
15     //如果是被点击的项,变换颜色
16     if (position==this.itemClicked) {
17         convertView.setBackgroundColor(Color.RED);
18     }else {
19         convertView.setBackgroundColor(Color.WHITE);
20     }
21     return convertView;
22 }
23     @Override
24     public void onItemClick(AdapterView<?> parent, View view, int position,
25             long id) {
26         //设置某项被点击
27         itemClicked=position;
28         this.notifyDataSetChanged();
29     }
30
31 }

 

下面就让我们跟进去MyArrayAdapter.notifyDataSetChange()中看看。在本文中,我所查看的Android源代码是4.4.0的,不同版本可能有所出入。

1     public void notifyDataSetChanged() {
2         super.notifyDataSetChanged();
3         mNotifyOnChange = true;
4     }

源代码就简单两句话,那么继续看看super是什么?

public class ArrayAdapter<T> extends BaseAdapter implements Filterable 

从类的声明中,父类就是ArrayAdapter,而ArrayList的父类是BaseAdapter。我们跟进BaseAdapter中看看。

 1 public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
 2     private final DataSetObservable mDataSetObservable = new DataSetObservable();
 3     //...省略不必要的代码
 4     public void registerDataSetObserver(DataSetObserver observer) {
 5         mDataSetObservable.registerObserver(observer);
 6     }
 7
 8     public void unregisterDataSetObserver(DataSetObserver observer) {
 9         mDataSetObservable.unregisterObserver(observer);
10     }
11
12     public void notifyDataSetChanged() {
13         mDataSetObservable.notifyChanged();
14     }
15
16     public void notifyDataSetInvalidated() {
17         mDataSetObservable.notifyInvalidated();
18     }
19     //...省略不必要的代码
20 }

我们发现其实就是DataSetObservable这个对象在发生作用,但是DataSetObservable这个对象估计就是一个简单的观察者的实现,Android框架的编写者不大可能将业务逻辑放在这里面,不过我们还是要确认是不是跟我们所想的一样。

 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             for (DataSetObserver observer : mObservers) {
 9                 observer.onChanged();
10             }
11         }
12     }
13
14     /**
15      * Invokes onInvalidated on each observer. Called when the data set being monitored
16      * has changed such that it is no longer valid.
17      */
18     public void notifyInvalidated() {
19         synchronized (mObservers) {
20             for (DataSetObserver observer : mObservers) {
21                 observer.onInvalidated();
22             }
23         }
24     }
25 }

果然,跟预想的一样,它只是简单地调用了绑定在它身上的回调接口。那么BaseAdapter.notifyDataSetChange()的接口具体是在哪里绑定的呢?很有可能在构造函数中绑定,我们跟进ArrayListAdapter看看。

 1     public ArrayAdapter(Context context, int textViewResourceId, List<T> objects) {
 2         init(context, textViewResourceId, 0, objects);
 3     }
 4
 5     private void init(Context context, int resource, int textViewResourceId, List<T> objects) {
 6         mContext = context;
 7         mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
 8         mResource = mDropDownResource = resource;
 9         mObjects = objects;
10         mFieldId = textViewResourceId;
11     }

ArrayListAdapter中有很多构造函数,但是几经辗转全部都会转到init()函数中,很遗憾,我们扑空了。那么还在哪里可能绑定notifyDataSetChange()回调函数呢?其实从MainActivity中Adapter的初始化过程中,基本上只能锁定在MainActivity第十行中setAdapter函数中。接下去看看 public void setAdapter(ListAdapter adapter)这个函数。 

 1     public void setAdapter(ListAdapter adapter) {
 2         if (null != mAdapter) {
 3             mAdapter.unregisterDataSetObserver(mDataSetObserver);
 4         }
 5
 6         resetList();
 7         mRecycler.clear();
 8
 9         if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
10             mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
11         } else {
12             mAdapter = adapter;
13         }
14
15         mOldSelectedPosition = INVALID_POSITION;
16         mOldSelectedRowId = INVALID_ROW_ID;
17         if (mAdapter != null) {
18             mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
19             mOldItemCount = mItemCount;
20             mItemCount = mAdapter.getCount();
21             checkFocus();
22
23             mDataSetObserver = new AdapterDataSetObserver();
24             mAdapter.registerDataSetObserver(mDataSetObserver);
25
26             mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
27
28             int position;
29             if (mStackFromBottom) {
30                 position = lookForSelectablePosition(mItemCount - 1, false);
31             } else {
32                 position = lookForSelectablePosition(0, true);
33             }
34             setSelectedPositionInt(position);
35             setNextSelectedPositionInt(position);
36
37             if (mItemCount == 0) {
38                 // Nothing selected
39                 checkSelectionChanged();
40             }
41
42             if (mChoiceMode != CHOICE_MODE_NONE &&
43                     mAdapter.hasStableIds() &&
44                     mCheckedIdStates == null) {
45                 mCheckedIdStates = new LongSparseArray<Boolean>();
46             }
47
48         } else {
49             mAreAllItemsSelectable = true;
50             checkFocus();
51             // Nothing selected
52             checkSelectionChanged();
53         }
54
55         if (mCheckStates != null) {
56             mCheckStates.clear();
57         }
58
59         if (mCheckedIdStates != null) {
60             mCheckedIdStates.clear();
61         }
62
63         requestLayout();
64     }

setAdapter(...)这个函数有点长,不过我们只需要关注跟notifiDataSetChange()有关的实现,也就是第23、24行。不过这里另一个值得关注的点就是第63行,requestLayout()这个函数,它主要就是用来刷新界面,让界面重新绘制的。在23,、24行,绑定了一个AdapterDataSetObserver对象,下面我们就跟进去看看。从前面DataSetObservable的实现中,我们知道了它在notifyDataSetChange()的时候会调用DataSetObserver的onChange()。

 1   class AdapterDataSetObserver extends DataSetObserver
 2   {
 3     private Parcelable mInstanceState = null;
 4
 5     AdapterDataSetObserver() {
 6     }
 7     public void onChanged() { mDataChanged = true;
 8       mOldItemCount = mItemCount;
 9       mItemCount = getAdapter().getCount();
10
11       if ((getAdapter().hasStableIds()) && (mInstanceState != null) && (mOldItemCount == 0) && (mItemCount > 0))
12       {
13         onRestoreInstanceState(mInstanceState);
14         mInstanceState = null;
15       } else {
16         rememberSyncState();
17       }
18       checkFocus();
19       requestLayout();
20     }
21     //...省略不必要代码
22 }

终于,在第19行,我们看见了requestLayout(),它就是用来重绘界面的,它在ViewRootImpl.java中有具体的实现。

1     public void requestLayout() {
2         checkThread();
3         mLayoutRequested = true;
4         scheduleTraversals();
5     }

关于scheduleTraversals()的实现,涉及到Android中View的绘制流程,感兴趣的可以看看《Android视图状态及重绘流程分析,带你一步步深入了解View(三)》。

    到了这里,我们就清楚了notifyDataSetChange()背后的实现机制了,在不知不觉之间Android框架帮我们干了很多事情,不过需要提醒的时,每一次notifyDataSetChange()都会引起界面的重绘。当需要修改界面上View的相关属性的时候,最后先设置完成再调用notifyDataSetChange()来重绘界面。

 

 

 

作者:kissazi2 
出处:http://www.cnblogs.com/kissazi2/ 
本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

转载:http://www.cnblogs.com/kissazi2/p/3721941.html

时间: 2025-01-13 05:25:00

从源代码的角度分析--在BaseAdapter调用notifyDataSetChanged()之后发生了什么的相关文章

从网站“优化”角度分析产品页面设计需要注意哪些要点

中介交易 SEO诊断 淘宝客 云主机 技术大厅 其实说实话做网站不在乎规模的大小,并不是说草根站长就不能创造奇迹,想想自己做目前这个网站一年多了,很多关键词也做到了相关理想的位置,盈利自然水到渠成,有的朋友就会问我为什么他们的网站坚持不下去呢?有时候问题出在站长自身,你把网站当回事,网站自然把你当回事,虽然是中小型网站,我们一样要细心地设计产品页面,只有这样才会让自己的网站得到长远的发展. 就像笔者之前说的"从网站优化角度分析"一样,下面我们就从搜索引擎的视觉解释一下产品页面布局的相关

从程序员的角度分析微信小程序

昨天朋友圈被微信小程序刷爆了. 我赶快在书架上拿出三年前买的书,把上面的土擦干净,压压惊. 作为一个并不是资深的程序员. 从程序员的角度分析一下微信小程序,欢迎指点. 首先吐槽 微信小程序只发了200个邀请号,和我预想的一样,张小龙并没有翻我牌,难道就不能雨露均沾吗? 先来了解下什么是微信小程序. 转自知乎 微信也许重申了"我们是一款约炮软件" 微信还提供了一大堆接口和组件(不好意思,说了句废话). 下面是禅叔的观点: 小程序原理就是用JS调用底层native组件,和React Nat

站在SEO的角度分析title长短所带来的影响

站点seo的角度来看title长度是否会对我们的站点优化产生影响?这个问题的答案是非常肯定的.但是具体要长好还是短好,可能有的人就知之甚少了.按照TF-IDF算法和HillTop算法,title不要太长对于站点的SEO有利;但从长尾角度分析流量的话,title则需要含有一些我们的目标用户常用的搜索词.当然我们还需要从各个不同的行业进行具体的分析,不可一概而论. title是SEO最重要的因素之一,在接手一个项目之后,无论你是新手还是老鸟,都会拿title做为优先的优化对象.其原因主要有以下三点:

从朋友角度分析一下友情链接

网上谈到友情链接的比比皆是,今天小弟不才,从朋友的角度分析一下友情链接对你网站的帮助.如果你觉得对你有所帮忙,那么我非常荣幸帮助你.好了废话不说,下面开始介绍. 比喻词解释:朋友(这里指友情练级) 地位(这里指权重) 友情链接介绍(看人先看他身边朋友) 先说一下什么叫做友情链接呢?友情链接就像是你的朋友,别人在首页或者整站加上你的友情链接,就是告诉他的访客(蜘蛛或者浏览者)这个人权重和我是一样的.他是我的一个朋友,访客到你朋友家做客,他如果看到你的网站,如果你的朋友地位(这里指权重)很好,那么从

以SEO与UEO的角度分析外链相关度的重要性

外链这一块相信很多从事优化工作的朋友都很重视,因为它是站点优化中不可或缺的一部分.而作为优化的新手,我们可能在刚开始都是只是在注重外链的数量上,而忽视外链的相关度问题,而恰巧外链的相关度确是一个很关键的因素.那么为什么呢?笔者将从SEO及UEO两个方向上分析为什么说外链的相关度很重要 一.从SEO角度分析 随着SEO技术的普及与改进,出现了很多相当牛x的SEOer,这些SEOer可以在不同行业类型的站点发布外链,但是搜索引擎真的能够识别我们发的内容是什么,内容的质量好坏吗?或许搜索引擎可以判断一

从数据结构的角度分析 for each in 比 for in 快的多

今天仔细琢磨了会,从数据结构的角度分析了下,觉得for in和for each in效率上有着本质的区别,无论是JS还是AS   之前听说火狐的JS引擎支持for each in的语法,例如下述的代码: 复制代码 代码如下: var arr = [10,20,30,40,50]; for each(var k in arr) console.log(k); 即可直接遍历出arr数组的内容. 由于只有FireFox才支持,所以几乎所有的JS代码都不用这一特征. 不过在ActionScript里天生

纯粹的 K12 精髓:从马列主义角度分析如何教孩子看图写话

纯粹的 K12 精髓:从马列主义角度分析如何教孩子看图写话 太阳火神的美丽人生 (http://blog.csdn.net/opengl_es) 本文遵循"署名-非商业用途-保持一致"创作公用协议 转载请保留此句:太阳火神的美丽人生 -  本博客专注于 敏捷开发及移动和物联设备研究:iOS.Android.Html5.Arduino.pcDuino,否则,出自本博客的文章拒绝转载或再转载,谢谢合作. 摘自百度百科<辩证唯物主义> 孩子对事物的认识,也是遵从这个过程的. 第一

【安全课堂】九大角度分析数据库安全漏洞

本文讲的是[安全课堂]九大角度分析数据库安全漏洞,数据库漏洞的存在有多种方式,由于每一个现实的场景由多维组合而成,因此数据库漏洞对应也可以从不同角度归类划分.这种分类将更有利于我们掌握对每种漏洞的防护技术. 安华金和数据库安全实验室主要从以下九个角度对数据库漏洞进行分类介绍:‍‍‍‍ ‍‍1.从漏洞作用范围划分 ‍‍‍‍远程漏洞:攻击者可以利用并直接通过网络发起对数据库攻击的漏洞.这类漏洞危害极大,攻击者能随心所欲的通过此漏洞危害网络上可辨识的数据库.此类漏洞为黑客利用漏洞的主力. ‍‍‍‍‍‍

从数据结构的角度分析 for each in 比 for in 快的多_javascript技巧

之前听说火狐的JS引擎支持for each in的语法,例如下述的代码: 复制代码 代码如下: var arr = [10,20,30,40,50];for each(var k in arr)console.log(k); 即可直接遍历出arr数组的内容. 由于只有FireFox才支持,所以几乎所有的JS代码都不用这一特征. 不过在ActionScript里天生就支持for each的语法,不论Array还是Vector,还是Dictionary,只要是可枚举的对象都可以for in和for