Android源码中的设计模式 —— RecyclerView

在刚开始使用ListView或者RecyclerView的时候,就一直懵懵懂懂的,感觉它的使用与其它控件相比,稍显复杂。尤其在使用Adapter的时候,就一直困惑Adapter是干嘛的?为什么叫Adapter呢?虽然会使用了,但困惑仍然在,心里很不爽。直到随着使用次数的增加,再加上多次的思考,以及后来开始结合设计模式(真的是相见恨晚),才慢慢有了点感觉。这篇文章,就结合设计模式、源码、我的思考以及其它参考,来一解心中的疑惑。

因为RecyclerView是ListView的继承和发展,有了RecyclerView,ListView已经可以弃之不用了,所以本文讲的是RecyclerView,但为了更好的理解,也会通过讲解ListView来进行一个过渡。文中若有讲解的有误和理解不到位的地方,还望批评指正,希望这篇文章也对您也有所帮助。

要想更好的理解RecyclerView的设计,结合它所使用的设计模式是非常有必要的,之前之所以感到RecyclerView很不好理解,就是因为不懂设计模式,开发者使用了一些很经典,但我却从来不知道的“套路”。在RecyclerView中,使用了两个很明显的设计模式:适配器模式和观察者模式。下面,我就透过这两个模式在RecyclerView中的运用,来揭开一小部分RecyclerView的神秘面纱。如果对这两个设计模式还不太了解的同学,可以浏览我之前的一篇文章,或者参阅相关书籍。

一、 适配器模式

1、疑问###

使用过RecyclerView的同学都知道,虽然我们使用的是它,但更多的代码却写给了Adapter,可见Adapter很重要,但也不太好懂。Adapter翻译过来就是“适配器”,很明显它是想实现一个适配的目的,但问题是谁和谁进行适配呢?之前也看了不少文章,绝大多数都说是“data和view进行适配”,听起来是挺有道理的。而且适配器模式本身就是起一个粘合剂的作用,这里说将data和view进行数据的绑定,这样解释也没有什么问题。但如果细想,还是会心生疑问:

适配器模式有三个角色:Target(目标角色)、Adaptee(需要适配的接口)、Adapter(适配器)。在RecyclerView中,Adapter就是适配器本身,这个没什么可说的。那data和view谁是Target,谁是Adaptee呢?如果按照那套解释,data是Adaptee,也就是被适配者,view是Target,也就是我们的目标。

但是data和view是两个不同的层次,而这里却说是把data通过适配器转化成view,总感觉这么说很牵强。而且我们都知道MVP模式,M是数据层,V是视图层,在这里,我们也没说它们之间有“适配”的关系啊,并没有说通过适配器把M适配成V。

所以,这套解释说不通,那RecyclerView中的适配器模式一定是用到了其它的地方,所以还是追溯源码,得到的答案更靠谱一些。

2、分析源码###

我们这里分析ListView的源码,之所以要从ListView看起,是因为RecyclerView是对ListView的进一步封装和优化,但实现逻辑和思想基本没变化,所以看ListView的源码更原始,更容易寻找答案。

ListView所要解决的一个最重要的问题就是:将每一项Item的视图输出。ListView允许我们自定义ItemView,所以ItemView的样式千变万化,而不管我们如何定义我们的ItemView,ListView都可以很好的自动的将其显示,那它是如何做到的呢?

先进入ListView的layoutChildren()方法,这个方法负责对所有的子元素进行布局:

@Override
    protected void layoutChildren() {

    …………

    try {
            super.layoutChildren();

            invalidate();
            switch (mLayoutMode) {

            …………

            case LAYOUT_FORCE_BOTTOM:
                sel = fillUp(mItemCount - 1, childrenBottom);
                adjustViewsUpOrDown();
                break;
            case LAYOUT_FORCE_TOP:
                mFirstPosition = 0;
                sel = fillFromTop(childrenTop);
                adjustViewsUpOrDown();
                break;
            }
        default:
        ……
    …………
        
    }

在该方法中,根据设定的布局模式来布局ItemView,然后我们进入其中一个查看即可,我们进入fillUp()

private View fillUp(int pos, int nextBottom) {
        View selectedView = null;

        int end = 0;
        if ((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
            end = mListPadding.top;
        }

        while (nextBottom > end && pos >= 0) {
            boolean selected = pos == mSelectedPosition;
            //通过下面的方法获得ItemView
            View child = makeAndAddView(pos, nextBottom, false, mListPadding.left, selected);
            nextBottom = child.getTop() - mDividerHeight;
            if (selected) {
                selectedView = child;
            }
            pos--;
        }

        mFirstPosition = pos + 1;
        setVisibleRangeHint(mFirstPosition, mFirstPosition + getChildCount() - 1);
        return selectedView;
    }

从代码可以看出,该布局方式(其它也是如此)会调用makeAndAddView()方法获取一个View,这个View就是ListView中的每一个Item,我们继续跟进:

 private View makeAndAddView(int position, int y, boolean flow, int childrenLeft,
            boolean selected) {
        if (!mDataChanged) {
            final View activeView = mRecycler.getActiveView(position);
            if (activeView != null) {
                setupChild(activeView, position, y, flow, childrenLeft, selected, true);
                return activeView;
            }
        }

        final View child = obtainView(position, mIsScrap);  //获取一个ItemView
        setupChild(child, position, y, flow, childrenLeft, selected, mIsScrap[0]);  //将ItemView设置到对应的地方

        return child;
    }

该方法主要有两步:首先调用obtainView()获取一个ItemView;然后调用setupChild()将这个View布局到特定位置。下面我们进入获取一个View的方法,也就是obtainView()

View obtainView(int position, boolean[] outMetadata) {
    …………

        final View scrapView = mRecycler.getScrapView(position);//从缓存的ItemView中获取,ListView的复用机制

        final View child = mAdapter.getView(position, scrapView, this);//调用getView()方法获得ItemView
        if (scrapView != null) {
            if (child != scrapView) {
                mRecycler.addScrapView(scrapView, position);
            } else if (child.isTemporarilyDetached()) {
                outMetadata[0] = true;
                child.dispatchFinishTemporaryDetach();
            }
        }

        …………

        return child;
    }
```
从代码可以看出:该方法首先调用```getScrapView```获取缓存的View,如果存在,则传入```getView()```的第二个参数,```getView()```方法是由我们用户自己来实现的,我们一般会判断convertView是否为空,如果为空则从xml中创建一个新的视图,否则使用缓存的View。所以,```getView()```方法获取的是一个View。
###3、解答###
我们总结一下上述的过程:ListView通过调用Adapter的```getView()```方法来获取一个ItemView,这个方法返回的是一个View类型的对象。所以,不管我们如何定义自己的ItemView,尽管它千变万化,它都是View的子类,通过依赖抽象的原则和Adapter模式就将ItemView的变化隔离了,保证了ListView的高度可定制化。在获取了View之后,再将这些View放到对应的位置上,再加上其复用机制,整个ListView就运转起来了。所以,Adapter的适配作用是将ItemView统一适配成了View,角色对应如下:

* Adaptee(被适配者):自定义的ItemView
* Target(目标):View
* Adapter:就是我们定义的Adapter

#二、观察者模式#
##1、疑问##
观察者模式在RecyclerView中表现的没有像适配器模式那么明显,但只要稍加思考,就会发现:在我们平时使用RecyclerView的时候,当往里面添加数据后,都会调用Adapter的```notifyDataSetChanged()```方法来更新界面显示。那你有没有想过,这个方法是如何工作的,为什么一个方法就全都搞定了。如果想要探寻它的工作原理,就必须要了解观察者模式,而当你学会了观察者模式,你就会特别想知道RecyclerView在这里是如何运用的,到底谁是观察者?谁是被观察者?

使用过RecyclerView的同学可能会对这个问题有自己的判断,就像我们上面提到的适配器模式一样有自己想法,虽然分析看起来很合理,但源码告诉我们事实并非如此。RecyclerView在这个模式的运用上,我一开始认为:data是被观察者,ItemView是观察者,当data发生变化时,就会通知ItemView进行相应的操作。是不是听起来也挺合理的,但这只是我一厢情愿的猜测,事实并非如此,到底谁是观察者?谁是被观察者?我们下面就来跟踪源码。
##2、分析源码##
我们首先就进入```notifyDataSetChanged()```方法,它定义在Adapter中:
```
public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }
```
代码我没有做任何删除,的确就只有这一行。从这一行代码的变量名称上我们就可以看出,它确实是使用了观察者模式,因为出现了```mObservable```,很明显这个变量是被观察者,那么我们只要找到它是在哪里创建的实例,我们就可以确定谁是观察者,那我们接下来就开始追溯它是在哪被创建的:
```
public static abstract class Adapter<VH extends ViewHolder> {
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        ……
}
```
找到了,```mObservable```是在Adapter中被实例化的,它的类是```AdapterDataObservable ```,所以它就是被观察者。而Adapter保有一个AdapterDataObservable 的引用,所以我们是不是可以说:Adapter就是被观察者?

如果Adapter是通过继承的方式来成为被观察者的,那毫无疑问它就是被观察者,不过这里是使用的组合的方式,组合可以实现与继承同样的效果,但组合在概念上是“has-a”的关系,而继承是“is-a”的关系,这样一来,我们只能说Adapter包含被观察者,但不能说它就是被观察者。

不过我觉得,在理解上,我们可以把Adapter当成被观察者。

找到了被观察者,我们继续跟踪,去寻找观察者,我们接着进入```notifyChanged()```方法:
```
public void notifyChanged() {
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
```
这个代码很简单,就是遍历所有观察者,然后调用它们的```onChanged()```方法,从而告知观察者发生了变化。mObservers是一个ArrayList,里面保存了已经注册过的所有的观察者,那它是在哪里被注册的呢?其实这些观察者就是RecyclerView通过```setAdapter```方法注册的,我们进入这个方法:
```
public void setAdapter(Adapter adapter) {
        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) {
            removeAndRecycleViews();
        }
        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();
    }
```
注册的逻辑代码找到了,而且我们还找到了```mObserver```变量,这个就是观察者的实例,我们追溯去看看其是在哪里被实例化的:
```
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild {

    …………

    private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

    …………
}
```
从代码可以看出,变量```mObserver ```的类是```RecyclerViewDataObserver ```,是在RecyclerView 中被实例化,RecyclerViewDataObserver 就是观察者,同上面一样,也是通过组合的方式,让RecyclerView 保有一个RecyclerViewDataObserver 的实例,那我们就可以说,RecyclerView就是一个观察者。
##3、解答##
1)
* 观察者:RecyclerViewDataObserver ,也就是RecyclerView。
* 被观察者:AdapterDataObservable,也就是Adapter

2)我们再捋一捋上面的代码,整理一下数据更新的整个过程:在数据数量发生变更时,开发者手动调用```Adapter.notifyDataSetChanged()```,而```notifyDataSetChanged()```实际上会调用```AdapterDataObservable```的```notifyChanged()```方法,该方法会遍历所有观察者的```onChanged()```方法。在```RecyclerViewDataObserver ```的```onChanged()```方法中会获取Adapter中数据集的新数量,然后调用```Recyclerview```的```requestLayout()```方法进行重新布局(我在本文没有给出这段代码,但就是这样),然后更新用户界面。

参考:《Android源码设计模式》

完。
    原文作者:thinkChao
    原文地址: https://www.jianshu.com/p/b35d0294e707
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞