ListView中的观察者模式

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

虽然现在RecyclerView很好用,也在逐渐替代ListView。很多github的开源大神也在对其进行更加实用的封装。我现在写的一个音乐播放器也在使用RecyclerView。但是这些都不阻碍我们学习ListView优秀的源码设计。

播放器我想要写的精美,但现在越写越多bug,这也应该是我离开大学校园,实习前的最后一个小作品了,接下来的半年多的时间要冲刺复习咯。

进入正题,我用的是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就是通过Adapter模式,观察者模式,子项复用机制实现了视图良好的扩展性,节约了内存开销,提高了运行效率

时间: 2024-08-28 20:47:59

ListView中的观察者模式的相关文章

ListView中使用自定义Adapter及时更xin

在项目中,遇到不能ListView及时更新的问题.写了一个demo,其中也遇到一些问题,一并写出来.好吧,上代码: public class PersonAdapter extends BaseAdapter { private ArrayList<PersonBean> mList; private Context mContext; public PersonAdapter(ArrayList<PersonBean> list, Context context) { mList

【安卓笔记】数据适配器(adapter)中的观察者模式

ListView要想显示数据,需要用到数据适配器即Adapter.而当我们删除ListView的某个条目时,数据适配器中的数据源必然发生改变,这时候我们通过调用适配器类提供的notifyDataSetChanged方法通知listview数据发生改变,请求重新绘制. 这其中其实使用了一种比较常见的设计模式,即观察者模式. 在分析数据适配器中涉及到的观察者模式之前,我们先简单了解下什么是观察者模式. 观察者模式的定义:定义对象间的一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象

C# 将Access中时间段条件查询的数据添加到ListView中

C# 将Access中时间段条件查询的数据添加到ListView中 一.让ListView控件显示表头的方法 在窗体中添加ListView 空间,其属性中设置:View属性设置为:Detail,Columns集合中添加表头中的文字. 二.利用代码给ListView添加Item. 首先,ListView的Item属性包括Items和SubItems.必须先实例化一个ListIteView对象.具体如下: ListViewItem listViewItem=new ListViewItem(); l

[设计模式]NetworkManagementService中的观察者模式

观察者模式 观察者模式有如下角色 (1)被观察者(Subject) (2)观察者(Observer) public class Subject{ private: list<Observer> mObservers; protect: void onChange(){ for(int i=0; i<mObservers.size(); ++i){ mObservers.get(i).onAction(); } } public: void attach(Observer m){ mObs

Xamarin.Forms listview中的button按钮,实现带着参数返回上一级页面

今天在做列表显示的时候遇到一个问题,就是在ListView中如何才能让一个button的按钮工作并且包含参数呢? 其实有点类似于rep里的控件无法起获取一样.在Xamarin中,当你button绑定事件并不包含在listview的数据源中,那么这个按钮的事件便是无效的. 那么该怎么解决呢?找了一下午终于找到了解决方案 xaml: <AbsoluteLayout IsVisible="True" HorizontalOptions="EndAndExpand"&

Android学习之解决ListView中item点击事件和item中Button点击事件冲突问题

在ListView中添加Button后,如果只是单纯的加入而不加限制的话,ListView的onClick点击事件没有响应,因为Button获取了item的焦点,想要两者都可点击,需要加上如下限制: 在ListView的适配器中的布局文件中添加: (1)在布局文件的根元素上中添加属性android:descendantFocusability="blocksDescendants" (2)在Button中添加属性android:focusable="false"和a

PullScrollView详解(六)——延伸拓展(listview中getScrollY()一直等于0、ScrollView中的overScrollBy)

前言:经常说follow your heart.但等到真到这么一天的时候,却很艰难 相关文章: 1.<PullScrollView详解(一)--自定义控件属性>2.<PullScrollView详解(二)--Animation.Layout与下拉回弹>3.<PullScrollView详解(三)--PullScrollView实现>4.<PullScrollView详解(四)--完全使用listview实现下拉回弹(方法一)>5.<PullScroll

android 的ListView中,如何判断其内容已滚动到最顶部或者最底部?

根据这个方法检测: 1 getListView().setOnScrollListener(new OnScrollListener() { 2 @Override 3 public void onScrollStateChanged(AbsListView view, int scrollState) { 4 } 5 6 @Override 7 public void onScroll(AbsListView view, int firstVisibleItem, int visibleIte

ImageLoader在Listview中的使用

图片加载框架之ImageLoader 1_特点 1)多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等 2)支持随意的配置ImageLoader,例如线程池,图片下载器,内存缓存策略,硬盘缓存策略,图片显示选项以及其他的一些配置 3)支持图片的内存缓存,文件系统缓存或者SD卡缓存 4)支持图片下载过程的监听 5)根据控件(ImageView)的大小对Bitmap进行裁剪,减少Bitmap占用过多的内存 6)较好的控制图片的加载过程,例如暂停图片加载,重