读源码 - 用设计模式解析 RecyclerView

《读源码 - 用设计模式解析 RecyclerView》

前言

读源码常常陷入繁复的细节中,结果看了半天,感觉自己懂了点什么,又好像什么也没弄懂。所以要带着目的性去看源码,理清主干部分的思路。本文的目的是梳理源码中RecyclerView怎么根据数据源的变更,让Item更新UI的。由于要以观察者模式为解析刀,所以先来看看观察者模式的前世今生。还是那句话:希望能简单点把问题说清楚,避免高大上的定义,虚无缥缈的遣词造句。

1

JDK源码中的观察者模式

观察者模式可以简单理解成定报纸,如果你订阅了某报纸,等有新的报纸时,该报就会把新的报纸投递给所有的订阅报纸的人。即你订阅,有更新就会给到你。这个订阅者和报纸之间就构成了观察者和被观察者的关系。JDK有2个类是帮助开发者快速实现观察者模式的—Observable(抽象被观察者)Observer(抽象观察者)

public interface Observer {
    void update(Observable o, Object arg);
}

发现JDK实现的抽象观察者很简单,就是个接口,有个叫update的方法。这个方法是用来接收被观察者发过来的新消息,消息的内容是参数arge。

public class Observable {
    private boolean changed = false;
    private Vector
    
      obs; public Observable() { obs = new Vector<>(); } public synchronized void addObserver(Observer o) { if (o == null) throw new NullPointerException(); if (!obs.contains(o)) { obs.addElement(o); } } public synchronized void deleteObserver(Observer o) { obs.removeElement(o); } public void notifyObservers() { notifyObservers(null); } public void notifyObservers(Object arg) { Object[] arrLocal; synchronized (this) { if (!changed) return; arrLocal = obs.toArray(); clearChanged(); } for (int i = arrLocal.length-1; i>=0; i--) ((Observer)arrLocal[i]).update(this, arg); } public synchronized void deleteObservers() { obs.removeAllElements(); } protected synchronized void setChanged() { changed = true; } protected synchronized void clearChanged() { changed = false; } public synchronized boolean hasChanged() { return changed; } public synchronized int countObservers() { return obs.size(); } }
    

梳理下这个类

  • 有个Vector 类型的集合用来存放观察者对象,有相关的方法addObserver,deleteObserver来操作删减集合中的对象。即订阅了这个被观察者,我就调用addObserver添加一个观察者。
  • 变参的通知方法notifyObservers,关键点就是取出集合中的观察者对象,然后用一个for循环不断的调用每个订阅者的update方法,通知他们有新的消息。
  • 全局变量changed,就是一个标识位,看数据是否有更新

是不是突然感觉好简单,想徒手撸个源码一样的观察者模式?

2

用JDK提供的类型实现观察者模式

既然了解了原理,立即手痒难耐的撸个例子。先来一个报纸(NewspaperObservable)

public class NewspaperObservable extends Observable{
    public void notifyAllMan(String info)
    {
        setChanged();
        notifyObservers(info);
    }
}

再来个定报纸的人(ManObserver),收到新报纸,就打印报纸的内容

public class ManObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        System.out.print(arg);
    }
}

最后试试,订报纸的人能不能及时收到新报纸。

public class Test {
    public static void main(String[] arge)
    {
        NewspaperObservable newspaperObservable = new NewspaperObservable();
        newspaperObservable.addObserver(new ManObserver());
        newspaperObservable.addObserver(new ManObserver());
        newspaperObservable.notifyAllMan("have a new paper");
    }
}

3

Android源码中的观察者模式

Android Framework没有直接使用java中的观察者模式,而是有自己的实现,看懂了第一节中关键几点,相信这很简单。

public abstract class Observable
    
      { protected final ArrayList
     
       mObservers = new ArrayList
      
       (); 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(); } } }
      
     
    

Android中的被观察者维护了一个观察者的集合,通过registerObserver,unregisterObserver方法管理订阅者,跟JDK中的做法如出一辙。可是最最重要的通知订阅者更新的方法呢??一脸懵逼的接着往下看。。。

public class DataSetObservable extends Observable
    
      { 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(); } } } }
    

发现类DataSetObservable继承了Observable,并实现了通知方法。那为什么不在Observable中实现这些方法呢,跟JDK中的实现一样。我的理解是,这里的Observable使用了泛型,而交给子类去实现通知方法,子类可以灵活的定义观察者类型,并使用观察者中的方法。所以DataSetObservable已经深入到业务中了。观察者变化不大。

public abstract class DataSetObserver {
    public void onChanged() {
        // Do nothing
    }

    public void onInvalidated() {
        // Do nothing
    }
}

4

RecyclerView源码

终于可以开始我们最原始的问题:源码中RecyclerView怎么根据数据源的变更,让Item更新UI的?秘密应该在RecyclerView,RecyclerView.Adapter这两个类中。根据我们的使用经验,有重大嫌疑的方法有RecyclerView.setAdapter(),Adapter.notifyDataSetChanged()。。。本文正式从技术文变推理文。。。RecyclerView源码一看11090行,再看RecyclerView.Adapter少很多哇(内部类当然的),果断放上源码

public static abstract class Adapter
    
      { private final AdapterDataObservable mObservable = new AdapterDataObservable(); private boolean mHasStableIds = false; public abstract VH onCreateViewHolder(ViewGroup parent, int viewType); public abstract void onBindViewHolder(VH holder, int position); public void onBindViewHolder(VH holder, int position, List
      payloads) { onBindViewHolder(holder, position); } public final VH createViewHolder(ViewGroup parent, int viewType) { TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG); final VH holder = onCreateViewHolder(parent, viewType); holder.mItemViewType = viewType; TraceCompat.endSection(); return holder; } public final void bindViewHolder(VH holder, int position) { holder.mPosition = position; if (hasStableIds()) { holder.mItemId = getItemId(position); } holder.setFlags(ViewHolder.FLAG_BOUND, ViewHolder.FLAG_BOUND | ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN); TraceCompat.beginSection(TRACE_BIND_VIEW_TAG); onBindViewHolder(holder, position, holder.getUnmodifiedPayloads()); holder.clearPayload(); TraceCompat.endSection(); } public int getItemViewType(int position) { return 0; } public void setHasStableIds(boolean hasStableIds) { if (hasObservers()) { throw new IllegalStateException("Cannot change whether this adapter has " + "stable IDs while the adapter has registered observers."); } mHasStableIds = hasStableIds; } public long getItemId(int position) { return NO_ID; } public abstract int getItemCount(); public final boolean hasStableIds() { return mHasStableIds; } public void onViewRecycled(VH holder) { } public boolean onFailedToRecycleView(VH holder) { return false; } public void onViewAttachedToWindow(VH holder) { } public void onViewDetachedFromWindow(VH holder) { } public final boolean hasObservers() { return mObservable.hasObservers(); } public void registerAdapterDataObserver(AdapterDataObserver observer) { mObservable.registerObserver(observer); } public void unregisterAdapterDataObserver(AdapterDataObserver observer) { mObservable.unregisterObserver(observer); } public void onAttachedToRecyclerView(RecyclerView recyclerView) { } public void onDetachedFromRecyclerView(RecyclerView recyclerView) { } public final void notifyDataSetChanged() { mObservable.notifyChanged(); } public final void notifyItemChanged(int position) { mObservable.notifyItemRangeChanged(position, 1); } public final void notifyItemChanged(int position, Object payload) { mObservable.notifyItemRangeChanged(position, 1, payload); } public final void notifyItemRangeChanged(int positionStart, int itemCount) { mObservable.notifyItemRangeChanged(positionStart, itemCount); } public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { mObservable.notifyItemRangeChanged(positionStart, itemCount, payload); } public final void notifyItemInserted(int position) { mObservable.notifyItemRangeInserted(position, 1); } public final void notifyItemMoved(int fromPosition, int toPosition) { mObservable.notifyItemMoved(fromPosition, toPosition); } public final void notifyItemRangeInserted(int positionStart, int itemCount) { mObservable.notifyItemRangeInserted(positionStart, itemCount); } public final void notifyItemRemoved(int position) { mObservable.notifyItemRangeRemoved(position, 1); } public final void notifyItemRangeRemoved(int positionStart, int itemCount) { mObservable.notifyItemRangeRemoved(positionStart, itemCount); } }
    

一眼就看到了全局变量mObservable赤果果的站在第一行,然后看到方法registerAdapterDataObserver,unregisterAdapterDataObserver,我可以大胆的推测每个Item都是数据变更的观察者,而被观察者赫然就是AdapterDataObservable。而后面的代码

        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

        public final void notifyItemChanged(int position) {
            mObservable.notifyItemRangeChanged(position, 1);
        }

        public final void notifyItemChanged(int position, Object payload) {
            mObservable.notifyItemRangeChanged(position, 1, payload);
        }

        public final void notifyItemRangeChanged(int positionStart, int itemCount) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount);
        }

        public final void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) {
            mObservable.notifyItemRangeChanged(positionStart, itemCount, payload);
        }

        public final void notifyItemInserted(int position) {
            mObservable.notifyItemRangeInserted(position, 1);
        }

        public final void notifyItemMoved(int fromPosition, int toPosition) {
            mObservable.notifyItemMoved(fromPosition, toPosition);
        }

        public final void notifyItemRangeInserted(int positionStart, int itemCount) {
            mObservable.notifyItemRangeInserted(positionStart, itemCount);
        }

        public final void notifyItemRemoved(int position) {
            mObservable.notifyItemRangeRemoved(position, 1);
        }

        public final void notifyItemRangeRemoved(int positionStart, int itemCount) {
            mObservable.notifyItemRangeRemoved(positionStart, itemCount);
        }

这些通知数据变更的方法,包括节点的更新,全局的更新,无一例外的是由被观察者AdapterDataObservable发送更新通知后完成。那么下面只需要了解以下问题,就能回答最初的问题。

  1. AdapterDataObserver 和 AdapterDataObservable什么时候完成绑定的?
  2. AdapterDataObserver 怎么更新UI的?

那么是时候看RecyclerView.setAdapter()方法了。

public void setAdapter(Adapter adapter) {
        // bail out if layout is frozen
        setLayoutFrozen(false);
        setAdapterInternal(adapter, false, true);
        requestLayout();
    }

接着看setAdapterInternal()

private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        if (mAdapter != null) {
            mAdapter.unregisterAdapterDataObserver(mObserver);
            mAdapter.onDetachedFromRecyclerView(this);
        }
        if (!compatibleWithPrevious || removeAndRecycleViews) {
            // end all running animations
            if (mItemAnimator != null) {
                mItemAnimator.endAnimations();
            }
            // Since animations are ended, mLayout.children should be equal to
            // recyclerView.children. This may not be true if item animator's end does not work as
            // expected. (e.g. not release children instantly). It is safer to use mLayout's child
            // count.
            if (mLayout != null) {
                mLayout.removeAndRecycleAllViews(mRecycler);
                mLayout.removeAndRecycleScrapInt(mRecycler);
            }
            // we should clear it here before adapters are swapped to ensure correct callbacks.
            mRecycler.clear();
        }
        mAdapterHelper.reset();
        final Adapter oldAdapter = mAdapter;
        mAdapter = adapter;
        if (adapter != null) {
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        markKnownViewsInvalid();
    }

代码中很明确的调用了

adapter.registerAdapterDataObserver(mObserver);

完成了绑定。而mObserver是RecyclerViewDataObserver类型,RecyclerViewDataObserver继承了AdapterDataObserver 。那么更新UI的方法应该在RecyclerViewDataObserver中。

private class RecyclerViewDataObserver extends AdapterDataObserver {
        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            if (mAdapter.hasStableIds()) {
                // TODO Determine what actually changed.
                // This is more important to implement now since this callback will disable all
                // animations because we cannot rely on positions.
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            } else {
                mState.mStructureChanged = true;
                setDataSetChangedAfterLayout();
            }
            if (!mAdapterHelper.hasPendingUpdates()) {
                requestLayout();
            }
        }

        @Override
        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeInserted(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeInserted(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeRemoved(int positionStart, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        @Override
        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            assertNotInLayoutOrScroll(null);
            if (mAdapterHelper.onItemRangeMoved(fromPosition, toPosition, itemCount)) {
                triggerUpdateProcessor();
            }
        }

        void triggerUpdateProcessor() {
            if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
                ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
            } else {
                mAdapterUpdateDuringMeasure = true;
                requestLayout();
            }
        }
    }

最后看AdapterDataObservable发送通知的方法,通过这些notify方法通知观察者更新UI。

static class AdapterDataObservable extends Observable
    
      { public boolean hasObservers() { return !mObservers.isEmpty(); } public void notifyChanged() { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onChanged(); } } public void notifyItemRangeChanged(int positionStart, int itemCount) { notifyItemRangeChanged(positionStart, itemCount, null); } public void notifyItemRangeChanged(int positionStart, int itemCount, Object payload) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload); } } public void notifyItemRangeInserted(int positionStart, int itemCount) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeInserted(positionStart, itemCount); } } public void notifyItemRangeRemoved(int positionStart, int itemCount) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeRemoved(positionStart, itemCount); } } public void notifyItemMoved(int fromPosition, int toPosition) { for (int i = mObservers.size() - 1; i >= 0; i--) { mObservers.get(i).onItemRangeMoved(fromPosition, toPosition, 1); } } }
    

RecyclerViewDataObserver 接到通知就妥妥的更新UI了。

后记

基本上回答了文章开始的问题,没有在源码中迷路。。。。喜欢的童鞋帮忙戳喜欢,有问题也可以评论。

    原文作者:算法小白
    原文地址: https://juejin.im/entry/5776399d2e958a005579ef97
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞