从RecyclerView的源码了解观察者模式

定义

定义对象之前一种一对多的依赖关系,使得当一个对象改变状态,所有依赖这个对象的对象都会得到通知并且自动更新。

使用场景

  • 关联行为场景。ps:关联行为是可拆分的,不是“组合”关系
  • 事件多级触发场景
  • 跨系统的消息交互场景。比如消息队列事件总线的处理机制

结构和UML图

观察者模式UML.jpg
  • Subject:抽象主题,也就是被观察者(Observable)角色,抽象主题角色把所有的观察者的引用保存在一个集合中,每个主题都可以有任意数量的观察者,抽象主题提供一个接口么可以添加/删除观察者对象。
  • ConcreteSubject:具体主题,又名具体被观察者,该觉得将有关组状态保存进具体的观察者对象,在具体主题的内部状态发生改变时,给所有注册过的观察者发送通知。
  • Observer:抽象观察者,观察者的抽象类,定义了一个更新接口,使得在得到主题的更改通知时来更改自己。
  • ConcreteSubject:具体的观察者,实现了抽象观察者角色所定义的更新接口,以便在主题的状态改变时更新自身的状态。

简单示例

我们平时如果订阅了某个平台的博客,在有新的博客时会通过邮件等方式通知我们订阅者。这里我就以这个写一个简单的观察者模式的示例

抽象主题:

//抽象主题角色,Subject:被观察者
public abstract class Blog {
    protected final ArrayList<User> mObservers = new ArrayList();
    
    public void addUser(User user){
        mObservers.add(user);
    }
    
    public void removeUser(User user){
        mObservers.remove(user);
    }
    
    public void removeAll(){
        mObservers.clear();
    }
}

具体主题:

//具体主题角色,ConcreteSubject:具体被观察者
public class ConcreteBlog extends Blog{

    public ConcreteBlog() {
        // TODO Auto-generated constructor stub
    }
    
    public void notifyChanged(String str){
        for(int i=mObservers.size()-1;i>=0;i--){
            mObservers.get(i).update(str);
        }
    }

}

抽象观察者:

//抽象观察者角色
public abstract class User {

    public void update(String str) {
    }

}

具体的观察者:

public class ConcreteUser extends User {

    @Override
    public void update(String str) {
        System.out.println(str);
    }
}

Client:

public class Client {

    public Client() {
        // TODO Auto-generated constructor stub
    }
    
    public static void main(String[] args) {
        //创建被观察者
        ConcreteBlog blog=new ConcreteBlog();
        //创建观察者
        ConcreteUser user1=new ConcreteUser();
        ConcreteUser user2=new ConcreteUser();
        ConcreteUser user3=new ConcreteUser();
        //将观察者注册到可观察对象的观察者列表中
        blog.addUser(user1);
        blog.addUser(user2);
        blog.addUser(user3);
        
        blog.notifyChanged("发布博客了,快来看啊!");
    }

}}

RecyclerView源码中的观察者模式

在使用RecyclerView的时候,我们在每次更新了RecyclerView的数据后通常调用notifyDataSetChanged()方法来更新我们的视图,那么我们就从这里开始,一步步跟进到源码看看RecyclerView的观察者模式是怎么实现的

public static abstract class Adapter<VH extends ViewHolder> {
        //定义观察者
        private final AdapterDataObservable mObservable = new AdapterDataObservable();
        private boolean mHasStableIds = false;
        public abstract VH onCreateViewHolder(ViewGroup parent, int viewType);
        .........//省略
         public void registerAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.registerObserver(observer);
        }
        public void unregisterAdapterDataObserver(AdapterDataObserver observer) {
            mObservable.unregisterObserver(observer);
        }
        //通知所有观察者
        public final void notifyDataSetChanged() {
            mObservable.notifyChanged();
        }

由上可知,其实我们的RecyclerView.Adapter本质就是观察者模式,接着下一步我们跟进到notifyDataSetChanged()方法来看看

static class AdapterDataObservable extends Observable<AdapterDataObserver> {
        public boolean hasObservers() {
            return !mObservers.isEmpty();
        }
        //调用没一个观察者的onChanged()方法来通知他们被观察者发生了变化
        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);
            }
        }
       .......//省略

这样一看,原来就是在AdapterDataObservable.notifyChanged()方法中来遍历所有的观察者,调用他们的onChange()方法,来通知的。我特意还贴出了notifyItemRangeChanged()方法,我们发现都是通过一样的模式去通知观察者的。

这样下来,我们知道了被观察者(Observable)是怎么实现的,现在我们来找找看观察者(Observer)是从哪儿来的呢?

回想一下,平时我们RecyclerView关联数据的时候都是通过调用setAdapter()方法实现的,那我们进去看看

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

咦?该方法把adapter传进了setAdapterInternal()方法,我们再跟进去看看。其实看到最后调用requestLayout()方法来重绘View我们就知道观察者的注册操作肯定是在上面方法中完成。

//定义在RecyclerView中,RecyclerViewDataObserver继承与AdapterDataObserver
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

private void setAdapterInternal(Adapter adapter, boolean compatibleWithPrevious,
            boolean removeAndRecycleViews) {
        //如果当前已经存在一个adapter了,就先注销这个adapter对象的观察者
        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中,实质是注册到AdapterDataObservable中
            adapter.registerAdapterDataObserver(mObserver);
            adapter.onAttachedToRecyclerView(this);
        }
        if (mLayout != null) {
            mLayout.onAdapterChanged(oldAdapter, mAdapter);
        }
        //数据更新操作
        mRecycler.onAdapterChanged(oldAdapter, mAdapter, compatibleWithPrevious);
        mState.mStructureChanged = true;
        //将之前的所有已知的itemveiw设置invalid(ViewHolder标志位FLAG_UPDATE | FLAG_INVALID)
        markKnownViewsInvalid();
    }

通过以上的代码我们知道,在设置setAdapter的时候会构建一个RecyclerViewDataObserver,也就是我们的观察者(RecyclerViewDataObserver),然后将它注册到被观察者(AdapterDataObservable)中。

我们找到了被观察者是怎么和观察者关联的,但是具体RecyclerViewDataObserver是什么东西呢?内部是怎么闪现的呢?我们继续研究研究,上面代码中我提到RecyclerViewDataObserver继承与AdapterDataObserver,那我们先来看看他的父类

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

        public void onItemRangeChanged(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
            // fallback to onItemRangeChanged(positionStart, itemCount) if app
            // does not override this method.
            onItemRangeChanged(positionStart, itemCount);
        }

        public void onItemRangeInserted(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeRemoved(int positionStart, int itemCount) {
            // do nothing
        }

        public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
            // do nothing
        }
    }

基本父类都是空方法,没有具体的实现。我们在来看看RecyclerViewDataObserver是如何重写父类的onChanged()方法的

private class RecyclerViewDataObserver extends AdapterDataObserver {
        RecyclerViewDataObserver() {
        }

        @Override
        public void onChanged() {
            assertNotInLayoutOrScroll(null);
            mState.mStructureChanged = true;
            //
            setDataSetChangedAfterLayout();
            if (!mAdapterHelper.hasPendingUpdates()) {
                //重新布局
                requestLayout();
            }
        }
        ......//省略 
}

好了。目前我们就到此为止吧(其实后面的我还没去仔细研究),总结一下,当RecyclerView的数据发生变化时,调用Adapter的notifyDataSetChanged()方法,这个方法又会调用AdapterDataObservable的notifyChanged()方法。这个方法又会调用所有观察者的onChanged()方法,在onChanged方法里dui RecyclerView进行重新布局是的RecyclerView刷新界面。一个完整的观察者模式就出来了。

总结

最后我们更加容易懂的一句话来总结:我们在创建Adapter的时候构建了一个RecyclerViewDataObserver,并且在setAdapter()的时候注册进了Adapter(本质是注册到AdapterDataObservable)。在我们调用notifyDataSetChanged()方法的时候,其实就是调用了AdapterDataObservable的notifyChanged()方法,该方法会遍历所有观察者的onChanged()方法,该方法会调用RecyclerView重新布局。(好像这句话比较长哈)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容