• 从源代码的角度分析--在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()来重绘界面。

  • 相关阅读:
    Kali Linux软件更新日报20190622
    Maltego更新到4.2.4.12374
    Kali Linux又增加一个顶级域名kali.download
    Nessus提示API Disabled错误
    数据包分析中Drop和iDrop的区别
    快速识别Hash加密方式hashid
    识别哈希算法类型hash-identifier
    vue实现前端导出excel表格
    vue自动化单元测试
    Mock制作假数据
  • 原文地址:https://www.cnblogs.com/kissazi2/p/3721941.html
Copyright © 2020-2023  润新知