最熟悉的陌生人: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());
        }
    }

在這里設置 Focus 和  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。

 

來自:http://www.androidchina.net/5724.html

 

 本文由用戶 llsa1738 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!