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

llsa1738 7年前
   <p>RecyclerView 得宠之前,ListView 可以说是我们用的最多的组件。之前一直没有好好看看它的源码,知其然不知其所以然。</p>    <p>今天我们来窥一窥 ListView 中的观察者模式。</p>    <p>在我们使用 ListView 的过程中,经常需要修改 Item 的状态,比如添加、删除、选中等等,通常的操作是在对数据源进行操作后,调用 notifyDataSetChanged() ,比如:</p>    <pre>  <code class="language-java">public void addData(String data) {          if (mData != null) {              mData.add(data);              notifyDataSetChanged();          }      }</code></pre>    <p>随后 ListView 中的数据就会更新,我们可以猜到这个过程是把全部 Item View 重新绘制、数据绑定了一遍,这个场景跟观察者模式很一致, <strong>具体怎么实现的呢</strong> ?</p>    <h3>前方高能预警,代码太多看不下去的可以先翻到篇尾看看流程图,有点印象再回来继续啃的,不然容易晕。</h3>    <p>1.首先我们跟进去看下 <em>notifyDataSetChanged()</em> 源码,进入了系统的  <em>BaseAdapter</em> :</p>    <pre>  <code class="language-java">/**       * 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();      }</code></pre>    <p>看注释, <em>“通知观察者数据已经改变,任何和数据集绑定的 View 都应该刷新”</em> ,的确是观察者模式。</p>    <p>那发布者、观察者是谁?在什么时候注册的?观察者的 notifyChanged() 方法又做了什么呢?</p>    <p>2.在 BaseAdapter 中我们可以看到这几个方法:</p>    <pre>  <code class="language-java">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();      }      //省略无关代码  }</code></pre>    <p>BaseAdapter 提供了 注册订阅、解除订阅、提醒观察者数据更新、告诉观察者数据不可用 等关键方法。</p>    <p>其中 <em>DataSetObservable</em> 是发布者:</p>    <pre>  <code class="language-java">/**   * 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();              }          }      }  }</code></pre>    <p>可以看到 notifyChanged 方法的注释中,是 <strong>倒序遍历观察者集合</strong> 并进行通知,这是为了避免观察者列表的 iterator 被使用时,进行删除操作导致出问题。</p>    <p><em>DataSetObservable</em> 继承自  <em>Observable < DataSetObserver > </em> ,看下  <em>Observable</em> 源码:</p>    <pre>  <code class="language-java">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();          }      }  }</code></pre>    <p><em>DataSetObserver</em> 就是观察者抽象类,将来需要被具体观察者者继承:</p>    <pre>  <code class="language-java">/**   * 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      }  }</code></pre>    <p>了解发布者、观察者基类后,接下来去看下在什么时候进行注册、通知。</p>    <p>3.ListView.setAdapter 源码:</p>    <pre>  <code class="language-java">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();      }</code></pre>    <p>可以看到在 ListView.setAdapter 方法中,先解除旧的观察者,然后初始化了新的观察者 <em>AdapterDataSetObserver</em> 并注册。</p>    <p>而 AdapterDataSetObserver 定义在 ListView 的父类 AbsListView 中:</p>    <pre>  <code class="language-java">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();              }          }      }</code></pre>    <p>AdapterDataSetObserver 继承自 <em>AdapterView.AdapterDataSetObserver</em> ,在 onChanged 和 onInvalidated 方法中先调用 <em>AdapterView.AdapterDataSetObserver</em> 对应的方法,然后调用了  <em>mFastScroll.onSectionsChanged();</em></p>    <p>先看 AdapterView.AdapterDataSetObserver :</p>    <pre>  <code class="language-java">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;          }      }</code></pre>    <p>看 onChanged() 方法,这个方法中先后更新了 数据更新状态(mDataChanged ),数据数量,而由于 BaseAdapter.hasStableIds() 默认返回 false , 所以我们直接看 else 情况下 <em>rememberSyncState</em> 方法:</p>    <pre>  <code class="language-java">/**       * 保存屏幕状态       *       */      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;              }          }      }</code></pre>    <p>rememberSyncState 方法中针对是否选择了 item,保存了当前状态,重新绘制时会恢复状态。当我们滑动 ListView 后进行刷新数据操作,ListView 并没有滚动到顶部,就是因为这个方法的缘故。</p>    <p>回到 AdapterDataSetObserver.onChanged() 方法:</p>    <pre>  <code class="language-java">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();          }          //...  }</code></pre>    <p>保存数据状态后,进入 chekFocus 方法:</p>    <pre>  <code class="language-java">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());          }      }</code></pre>    <p>在这里设置 <em>Focus</em> 和  <em>FocusableInTouchMode</em> 状态。</p>    <p>最后终于到了 View 的重新绘制 <em>requestLayout</em> , 这里将遍历 View 树重新绘制:</p>    <pre>  <code class="language-java">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;          }      }</code></pre>    <p>至此,我们了解了 ListView 中的观察者模式的大概流程,看得人快吐血了,一层调一层啊,还是画个 UML 图和流程图来回顾一下:</p>    <h3>ListView 中的观察者模式</h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/150657b1feec278a5aaf1ac49b4be956.png"></p>    <h3>ListView 注册观察者 流程图 :</h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/f8b31169f9b8c1a24ce1c22327fdb2f3.png"></p>    <h3>ListView 通知观察者更新 流程图 :</h3>    <p style="text-align:center"><img src="https://simg.open-open.com/show/e36b13e6b0c5d60d987ca49f390e9a8a.png"></p>    <h2>备注:</h2>    <p>ListView 另外牛的一点就是可以加载各种各样的 Item View,这得益于当初设计的 Adapter。</p>    <p> </p>    <p>来自:http://www.androidchina.net/5724.html</p>    <p> </p>