ListView中的观察者模式

    xiaoxiao2022-06-23  26

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

    虽然现在RecyclerView很好用,也在逐渐替代ListView。Github 上也很多实用的封装。但是这些都不阻碍我们学习ListView优秀的源码设计。

    进入正题,我用的是Api-23的源码。接下来就从源码的角度带你学习ListView中的观察者模式

    当我们开启异步线程,向服务端拉取数据后,数据源已经更新了,此时想要更新ListView的视图以显示新的数据。 ListView使用了Adapter模式,很简单只需一行代码就能完成ListView的更新。

    mAdapter.notifyDataSetChanged();

    那么这里引出一个问题, 更新ListView的工作,是Adapter完成的还是ListView自身内部完成的?可以先猜想一下再往下看。 因为我之前已经学习过自定义控件,所以我看源码之前猜想是ListView完成的。惯性使然,我想到他可能是调用了onLayout(),onDraw()等方法呀,去重新布局,绘制

    那接下来就解开疑惑吧。

    先找到源头,从ListView绑定Adapter那里开始。

    mListView.setAdapter(mAdapter);

    ListView和Adapter就是用这行代码建立起关联的。

    那么跟踪setAdapter方法进去:

    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(); }

    方法是这样开始的

    if (mAdapter != null && mDataSetObserver != null) { mAdapter.unregisterDataSetObserver(mDataSetObserver); }

    先判断mAdapter != null && mDataSetObserver != null mAdapter肯定是不为null的,那么mDataSetObserver呢?这个引用是哪里被赋值的,先不管,继续往下看setAdapter方法。 这里先分享我看源码的方法吧: 刚开始的时候我是很喜欢往深处闯,导致看了一天都无法自拔,思路又散了。现在我看源码都是挑重点看,比如这个setAdapter方法,一路看下来都没有return 语句跳出,那么就一定会来到if(mAdapter !=null )这个判断,如下:

    if (mAdapter != null) { mAreAllItemsSelectable = mAdapter.areAllItemsEnabled(); mOldItemCount = mItemCount; mItemCount = mAdapter.getCount(); checkFocus(); mDataSetObserver = new AdapterDataSetObserver(); mAdapter.registerDataSetObserver(mDataSetObserver); //代码省略 }

    到了这里,我们也就找到了mDataSetObserver,原来是在这里被赋值的。 现在得出小结论: 1.在ListView的setAdapter方法中,生成了一个AdapterDataSetObserver对象并赋值给mDataSetObserver 2.调用Adapter的registerDataSetObserver方法将mDataSetObserver注册进去。

    现在我们好奇的是Adapter的registerDataSetObserver方法。继续前进。 在BaseAdapter类中找到了registerDataSetObserver方法,并且也找到了经常调用的,很熟悉的notifyDataSetChanged方法。如下:

    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter { private final DataSetObservable mDataSetObservable = new DataSetObservable(); public boolean hasStableIds() { return false; } public void registerDataSetObserver(DataSetObserver observer) { mDataSetObservable.registerObserver(observer); } public void unregisterDataSetObserver(DataSetObserver observer) { mDataSetObservable.unregisterObserver(observer); } /** * Notifies the attached observers that the underlying data has been changed * and any View reflecting the data set should refresh itself. */ public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); } /** * Notifies the attached observers that the underlying data is no longer valid * or available. Once invoked this adapter is no longer valid and should * not report further data set changes. */ public void notifyDataSetInvalidated() { mDataSetObservable.notifyInvalidated(); } //代码省略 }

    可以看到,在registerDataSetObserver方法中,又调用了DataSetObservable的registerObserver方法将传进来的AdapterDataSetObserver对象注册进去,那么这个DataSetObservable又是什么呢?继续跟进

    这个DataSetObservable源码比较少,那就全部贴出

    public class DataSetObservable extends Observable<DataSetObserver> { /** * Invokes {@link DataSetObserver#onChanged} on each observer. * Called when the contents of the data set have changed. The recipient * will obtain the new contents the next time it queries the data set. */ 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(); } } } /** * Invokes {@link DataSetObserver#onInvalidated} on each observer. * Called when the data set is no longer valid and cannot be queried again, * such as when the data set has been closed. */ public void notifyInvalidated() { synchronized (mObservers) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onInvalidated(); } } } }

    好像看不太懂。mObservers是什么?竟然没有registerObserver方法。哈哈,那肯定是父类继承下来的啊。在DataSetObservable类中暂时没我们想要知道的信息,那么就看看他的父类Observable吧。Observable还是个泛型。不管,看内部实现原理就好

    public abstract class Observable<T> { /** * The list of observers. An observer can be in the list at most * once and will never be null. */ protected final ArrayList<T> mObservers = new ArrayList<T>(); /** * Adds an observer to the list. The observer cannot be null and it must not already * be registered. * @param observer the observer to register * @throws IllegalArgumentException the observer is null * @throws IllegalStateException the observer is already registered */ public void registerObserver(T observer) { if (observer == null) { throw new IllegalArgumentException("The observer is null."); } synchronized(mObservers) { if (mObservers.contains(observer)) { throw new IllegalStateException("Observer " + observer + " is already registered."); } mObservers.add(observer); } } //代码省略 }

    找到了registerObserver方法。代码逻辑还挺简单的。 我们又可以得出小结论:

    DataSetObservable的内部维护着一个观察者集合,即源码中的mObservers。当我们的ListView绑定了Adapter,调用BaseAdapter的registerDataSetObserver方法时,实际上是在这个观察者集合mObservers里将该观察者添加进来。对ListView来说,这个观察者就是AdapterDataSetObserver 完成注册。以上就是setAdapter方法的源码分析

    再看到BaseAdapter的notifyDataSetChanged()方法

    public void notifyDataSetChanged() { mDataSetObservable.notifyChanged(); }

    内部调用了DataSetObservable的notifyChanged方法

    再回到DataSetObservable的源码,看到notifyChanged()方法

    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(); } } }

    从观察者集合里遍历出观察者,并调用该观察者的onChange()方法

    很清楚了吧。 当我们调用Adapter的notifyDataSetChanged方法更新ListView。 在notifyDataSetChanged方法中又会调用DataSetObservable的notifyChanged方法。 而从DataSetObservable的源码中,我们知道了在notifyChanged方法中又会遍历出 AdapterDataSetObserver(观察者),并调用这个观察者的onChanged()方法。 完毕,底层实现就是这样。 接下来只需要知道AdapterDataSetObserver(观察者)的onChanged()方法里做了什么就好了。 而AdapterDataSetObserver,是ListView的父类AdapterView的一个内部类。他是真的有onChanged方法的。不信你看

    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(); } //代码省略 }

    终于揭开谜底,在AdapterDataSetObserver的onChanged()方法里,实际上是调用了View的requestLayout()方法进行重新策略,布局,绘制整个ListView的子项item view requestLayout()的源码如下:

    public void requestLayout() { if (mMeasureCache != null) mMeasureCache.clear(); if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) { // Only trigger request-during-layout logic if this is the view requesting it, // not the views in its parent hierarchy ViewRootImpl viewRoot = getViewRootImpl(); if (viewRoot != null && viewRoot.isInLayout()) { if (!viewRoot.requestLayoutDuringLayout(this)) { return; } } mAttachInfo.mViewRequestingLayout = this; } mPrivateFlags |= PFLAG_FORCE_LAYOUT; mPrivateFlags |= PFLAG_INVALIDATED; if (mParent != null && !mParent.isLayoutRequested()) { mParent.requestLayout(); } if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) { mAttachInfo.mViewRequestingLayout = null; } }

    AdapterView是继承ViewGroup的,但是ViewGroup并没有重写requestLayout()方法。有能力的同学可以继续深入研究AdapterView到底是怎么重新布局的

    至此,我们已经解开了开篇的疑惑 综上所述,AdapterDataSetObserver这个是观察者,在AdapterDataSetObserver的onChanged函数中,实际上调用的是View中的方法完成了整个更新ListView的工作,AdapterDataSetObserver只是在外层进行了包装,真正的核心功能是ListView,更加准确的说话是ListView的父类AdapterView。

    可以换个角度理解,ListView 调用 setAdapter 方法,将一个观察者注入 Adapter 中维护的观察者序列,这样 ListView 和 Adapter 就建立起了关联,当有了数据变化,我们调用了 Adapter 的 notifyDataSetChanged 方法,内部会遍历出观察者,ListView是观察者,Adapter 是被观察者。ListView对Adapter订阅。有了数据更新后,Adapter通知ListView,告诉 ListView ,你可以更新视图了,之后 ListView调用 requestLayout 方法重新绘制界面,完成视图的更新。

    ListView就是通过Adapter模式,观察者模式,子项复用机制实现了视图良好的扩展性,节约了内存开销,提高了运行效率

    转载请注明原文地址: https://ju.6miu.com/read-1123517.html

    最新回复(0)