从源代码的角度分析–在BaseAdapter调用notifyDataSetChanged()之后发生了什么 – 陈哈哈 – 博客园

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

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

复制代码
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> MainActivity <span style="color: #0000ff;">extends</span><span style="color: #000000;"> Activity {</span>

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代码:

复制代码
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> MyArrayAdapter <span style="color: #0000ff;">extends</span> ArrayAdapter&lt;String&gt; <span style="color: #0000ff;">implements</span>

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的,不同版本可能有所出入。

 

<span style="color: #008080;">1</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> notifyDataSetChanged() {</span>

2 super.notifyDataSetChanged();

3 mNotifyOnChange = true;

4 }

 

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

 

<span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> ArrayAdapter&lt;T&gt; <span style="color: #0000ff;">extends</span> BaseAdapter <span style="color: #0000ff;">implements</span> Filterable

 

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

复制代码
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">abstract</span> <span style="color: #0000ff;">class</span> BaseAdapter <span style="color: #0000ff;">implements</span><span style="color: #000000;"> ListAdapter, SpinnerAdapter {</span>

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框架的编写者不大可能将业务逻辑放在这里面,不过我们还是要确认是不是跟我们所想的一样。

复制代码
<span style="color: #008080;"> 1</span> <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">class</span> DataSetObservable <span style="color: #0000ff;">extends</span> Observable&lt;DataSetObserver&gt;<span style="color: #000000;"> {</span>

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看看。

复制代码
<span style="color: #008080;"> 1</span>     <span style="color: #0000ff;">public</span> ArrayAdapter(Context context, <span style="color: #0000ff;">int</span> textViewResourceId, List&lt;T&gt;<span style="color: #000000;"> objects) {</span>

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)这个函数。 

复制代码
<span style="color: #008080;"> 1</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> setAdapter(ListAdapter adapter) {</span>

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()。

复制代码
<span style="color: #008080;"> 1</span>   <span style="color: #0000ff;">class</span> AdapterDataSetObserver <span style="color: #0000ff;">extends</span><span style="color: #000000;"> DataSetObserver</span>

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中有具体的实现。

 

<span style="color: #008080;">1</span>     <span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> requestLayout() {</span>

2 checkThread();

3 mLayoutRequested = true;

4 scheduleTraversals();

5 }

 

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

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

来源URL:http://www.cnblogs.com/kissazi2/p/3721941.html