• 最熟悉的陌生人:ListView 中的观察者模式


    RecyclerView 得宠之前,ListView 可以说是我们用的最多的组件。之前一直没有好好看看它的源码,知其然不知其所以然。

    今天我们来窥一窥 ListView 中的观察者模式。

    不熟悉观察者模式的可以看看这篇 观察者模式 : 一支穿云箭,千军万马来相见 巩固一下。

    在我们使用 ListView 的过程中,经常需要修改 Item 的状态,比如添加、删除、选中等等,通常的操作是在对数据源进行操作后,调用 notifyDataSetChanged() ,比如:

        public void addData(String data) {
            if (mData != null) {
                mData.add(data);
                notifyDataSetChanged();
            }
        }

    随后 ListView 中的数据就会更新,我们可以猜到这个过程是把全部 Item View 重新绘制、数据绑定了一遍,这个场景跟观察者模式很一致,具体怎么实现的呢

    前方高能预警,代码太多看不下去的可以先翻到篇尾看看流程图,有点印象再回来继续啃的,不然容易晕。

    1.首先我们跟进去看下 notifyDataSetChanged() 源码,进入了系统的 BaseAdapter

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

    看注释,“通知观察者数据已经改变,任何和数据集绑定的 View 都应该刷新”,的确是观察者模式。

    那发布者、观察者是谁?在什么时候注册的?观察者的 notifyChanged() 方法又做了什么呢?

    2.在 BaseAdapter 中我们可以看到这几个方法:

    public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
        private final DataSetObservable mDataSetObservable = new DataSetObservable();
    
        public boolean hasStableIds() {
            return false;
        }
    
        /**
        * BaseAdapter 提供了 注册订阅方法
        */
        public void registerDataSetObserver(DataSetObserver observer) {
            mDataSetObservable.registerObserver(observer);
        }
    
        /**
        * 还提供了 解除订阅方法
        */
        public void unregisterDataSetObserver(DataSetObserver observer) {
            mDataSetObservable.unregisterObserver(observer);
        }
    
        /**
         * 数据更新时通知观察者
         */
        public void notifyDataSetChanged() {
            mDataSetObservable.notifyChanged();
        }
    
        /**
         * 提醒观察者散了,别看了,数据不可用了
         * /
        public void notifyDataSetInvalidated() {
            mDataSetObservable.notifyInvalidated();
        }
        //省略无关代码
    }
    

    BaseAdapter 提供了 注册订阅、解除订阅、提醒观察者数据更新、告诉观察者数据不可用 等关键方法。

    其中 DataSetObservable 是发布者:

    /**
     * A specialization of {@link Observable} for {@link DataSetObserver}
     * that provides methods for sending notifications to a list of
     * {@link DataSetObserver} objects.
     */
    public class DataSetObservable extends Observable<DataSetObserver> {
        /**
         * 发出更新提醒
         */
        public void notifyChanged() {
            synchronized(mObservers) {
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
        }
    
        /**
         * 发出数据集无法使用通知
         */
        public void notifyInvalidated() {
            synchronized (mObservers) {
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onInvalidated();
                }
            }
        }
    }

    可以看到 notifyChanged 方法的注释中,是倒序遍历观察者集合并进行通知,这是为了避免观察者列表的 iterator 被使用时,进行删除操作导致出问题。

    DataSetObservable 继承自 Observable < DataSetObserver > ,看下 Observable 源码:

    public abstract class Observable<T> {
        /**
         * 观察者列表,不能重复,不能为空
         */
        protected final ArrayList<T> mObservers = new ArrayList<T>();
    
        /**
         * 注册一个观察者,不能重复,不能为空
         */
        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);
            }
        }
    
        /**
         * 解除注册一个观察者
         */
        public void unregisterObserver(T observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized(mObservers) {
                int index = mObservers.indexOf(observer);
                if (index == -1) {
                    throw new IllegalStateException("Observer " + observer + " was not registered.");
                }
                mObservers.remove(index);
            }
        }
    
        /**
         * 移除所有观察者
         */
        public void unregisterAll() {
            synchronized(mObservers) {
                mObservers.clear();
            }
        }
    }

    DataSetObserver 就是观察者抽象类,将来需要被具体观察者者继承:

    /**
     * DataSetObserver must be implemented by objects which are added to a DataSetObservable.
     */
    public abstract class DataSetObserver {
        /**
         * 数据改变时调用
         */
        public void onChanged() {
            // Do nothing
        }
    
        /**
         * 数据不可用时调用
         */
        public void onInvalidated() {
            // Do nothing
        }
    }

    了解发布者、观察者基类后,接下来去看下在什么时候进行注册、通知。

    3.ListView.setAdapter 源码:

    public void setAdapter(ListAdapter adapter) {
            //移除旧的观察者
            if (mAdapter != null && mDataSetObserver != null) {
                mAdapter.unregisterDataSetObserver(mDataSetObserver);
            }
    
            //省略不相关内容...
    
            if (mAdapter != null) {
                //...
    
                //初始化新观察者并注册
                mDataSetObserver = new AdapterDataSetObserver();
                mAdapter.registerDataSetObserver(mDataSetObserver);
    
                //...
                if (mItemCount == 0) {
                    // Nothing selected
                    checkSelectionChanged();
                }
            } else {
                mAreAllItemsSelectable = true;
                checkFocus();
                // Nothing selected
                checkSelectionChanged();
            }
    
            requestLayout();
        }

    可以看到在 ListView.setAdapter 方法中,先解除旧的观察者,然后初始化了新的观察者 AdapterDataSetObserver 并注册。

    而 AdapterDataSetObserver 定义在 ListView 的父类 AbsListView 中:

    class AdapterDataSetObserver extends AdapterView<ListAdapter>.AdapterDataSetObserver {
            @Override
            public void onChanged() {
                super.onChanged();
                if (mFastScroll != null) {
                    mFastScroll.onSectionsChanged();
                }
            }
    
            @Override
            public void onInvalidated() {
                super.onInvalidated();
                if (mFastScroll != null) {
                    mFastScroll.onSectionsChanged();
                }
            }
        }

    AdapterDataSetObserver 继承自 AdapterView.AdapterDataSetObserver,在 onChanged 和 onInvalidated 方法中先调用 AdapterView.AdapterDataSetObserver 对应的方法,然后调用了 mFastScroll.onSectionsChanged();

    先看 AdapterView.AdapterDataSetObserver :

    class AdapterDataSetObserver extends DataSetObserver {
    
            private Parcelable mInstanceState = null;
    
            @Override
            public void onChanged() {
                //更新 数据修改状态
                mDataChanged = true;
                //更新 数据数量
                mOldItemCount = mItemCount;
                //更新 ItemView 数量
                mItemCount = getAdapter().getCount();
    
                // 监测是否有数据之前不可用、现在可用
                // 由于 BaseAdapter.hasStableIds() 默认返回 false ,所以我们直接看 else
                if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                        && mOldItemCount == 0 && mItemCount > 0) {
                    AdapterView.this.onRestoreInstanceState(mInstanceState);
                    mInstanceState = null;
                } else {
                   //记录当前状态,接下来刷新时要用到这些状态
                    rememberSyncState();
                }
                checkFocus();
                requestLayout();
            }
    
            @Override
            public void onInvalidated() {
                mDataChanged = true;
    
                if (AdapterView.this.getAdapter().hasStableIds()) {
                    // Remember the current state for the case where our hosting activity is being
                    // stopped and later restarted
                    mInstanceState = AdapterView.this.onSaveInstanceState();
                }
    
                // Data is invalid so we should reset our state
                mOldItemCount = mItemCount;
                mItemCount = 0;
                mSelectedPosition = INVALID_POSITION;
                mSelectedRowId = INVALID_ROW_ID;
                mNextSelectedPosition = INVALID_POSITION;
                mNextSelectedRowId = INVALID_ROW_ID;
                mNeedSync = false;
    
                checkFocus();
                requestLayout();
            }
    
            public void clearSavedState() {
                mInstanceState = null;
            }
        }

    看 onChanged() 方法,这个方法中先后更新了 数据更新状态(mDataChanged ),数据数量,而由于 BaseAdapter.hasStableIds() 默认返回 false , 所以我们直接看 else 情况下 rememberSyncState 方法:

     /**
         * 保存屏幕状态
         *
         */
        void rememberSyncState() {
            if (getChildCount() > 0) {
                mNeedSync = true;
                mSyncHeight = mLayoutHeight;
                if (mSelectedPosition >= 0) {
                    //如果选择了内容,保存选择的位置和距离顶部的偏移量
                    View v = getChildAt(mSelectedPosition - mFirstPosition);
                    mSyncRowId = mNextSelectedRowId;
                    mSyncPosition = mNextSelectedPosition;
                    if (v != null) {
                        mSpecificTop = v.getTop();
                    }
                    mSyncMode = SYNC_SELECTED_POSITION;
                } else {
                    // 如果没有选择内容就保存第一个 View 的偏移量
                    View v = getChildAt(0);
                    T adapter = getAdapter();
                    if (mFirstPosition >= 0 && mFirstPosition < adapter.getCount()) {
                        mSyncRowId = adapter.getItemId(mFirstPosition);
                    } else {
                        mSyncRowId = NO_ID;
                    }
                    mSyncPosition = mFirstPosition;
                    if (v != null) {
                        mSpecificTop = v.getTop();
                    }
                    mSyncMode = SYNC_FIRST_POSITION;
                }
            }
        }

    rememberSyncState 方法中针对是否选择了 item,保存了当前状态,重新绘制时会恢复状态。当我们滑动 ListView 后进行刷新数据操作,ListView 并没有滚动到顶部,就是因为这个方法的缘故。

    回到 AdapterDataSetObserver.onChanged() 方法:

    class AdapterDataSetObserver extends DataSetObserver {
            @Override
            public void onChanged() {
                //更新 数据修改状态
                mDataChanged = true;
                //更新 数据数量
                mOldItemCount = mItemCount;
                //更新 ItemView 数量
                mItemCount = getAdapter().getCount();
    
                // 监测是否有数据之前不可用、现在可用
                // 由于 BaseAdapter.hasStableIds() 默认返回 false ,所以我们直接看 else
                if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                        && mOldItemCount == 0 && mItemCount > 0) {
                    AdapterView.this.onRestoreInstanceState(mInstanceState);
                    mInstanceState = null;
                } else {
                   //记录当前状态,接下来刷新时要用到这些状态
                    rememberSyncState();
                }
                checkFocus();
                requestLayout();
            }
            //...
    }

    保存数据状态后,进入 chekFocus 方法:

    
        void checkFocus() {
            final T adapter = getAdapter();
            final boolean empty = adapter == null || adapter.getCount() == 0;
            final boolean focusable = !empty || isInFilterMode();
            // The order in which we set focusable in touch mode/focusable may matter
            // for the client, see View.setFocusableInTouchMode() comments for more
            // details
            super.setFocusableInTouchMode(focusable && mDesiredFocusableInTouchModeState);
            super.setFocusable(focusable && mDesiredFocusableState);
            if (mEmptyView != null) {
                updateEmptyStatus((adapter == null) || adapter.isEmpty());
            }
        }

    在这里设置 FocusFocusableInTouchMode 状态,关于 FocusableInTouchMode 不熟悉的可以 查看这篇文章

    最后终于到了 View 的重新绘制 requestLayout, 这里将遍历 View 树重新绘制:

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

    至此,我们了解了 ListView 中的观察者模式的大概流程,看得人快吐血了,一层调一层啊,还是画个 UML 图和流程图来回顾一下:

    ListView 中的观察者模式

    这里写图片描述

    ListView 注册观察者 流程图 :

    这里写图片描述

    ListView 通知观察者更新 流程图 :

    这里写图片描述

    备注:

    设计模式代码在这里

    ListView 另外牛的一点就是可以加载各种各样的 Item View,这得益于当初设计的 Adapter,下篇文章我们来分析下 ListView 中的适配器模式。

  • 相关阅读:
    dolphin以root身份打开
    linux ACL权限
    PIT中断与外设时钟配置
    GPT定时器定时
    I.MX RT 时钟控制模块(CCM)
    OLED屏显示功能的实现
    RT1052 A/D数据采集
    滴答时钟与延时
    局域网 访问mac前端项目
    js 加减乘除取余运算
  • 原文地址:https://www.cnblogs.com/hehehaha/p/6147219.html
Copyright © 2020-2023  润新知