Android之观察者模式

作者: xiasuhuei321 | 来源:发表于2016-10-15 12:57 被阅读1524次

    写在前面

    以前听说过一句话,说是** 自己写过代码总量没有超过10w行谈设计模式那都是耍流氓 **。我信了,所以一直没怎么系统的看已经买了的《Android源码设计模式》。最近有个小伙伴在群里问recyclerview怎么刷新数据,以前大概也做过,流程也就是那么两步:1.更新Adapter里数据集的引用,让他指向最新的数据集。2.调用Adapter的notifyDataSetChanged()来更新ui。之后小伙伴又问了notifyDataSetChanged()到底如何更新ui的,当时只是看出了一个观察者模式,还有一些细节没想明白。而且讲真的观察者模式的应用还是非常多的,无论是Android还是最近很火的RxJava,其中都可以看到观察者模式的身影,所以决定这周把观察者模式撸一遍。

    1、什么是观察者模式

    观察者模式(Observer Pattern)定义了对象间一种一对多的依赖关系,使得每当一个对象改变状态,则所有依赖于它的对象都会得到通知并被自动更新,观察者模式又叫做发布-订阅(Publish/Subscribe)模式。

    定义总是这么精准而抽象,让我们结合一些Android中的场景来简单的理解一下:在Android最常用的点击事件,我们会通过设置控件的OnClickListener并传入一个OnClickListener的实现类来回调点击事件。这种我们便可以将之看做一个观察者模式。我们的OnClickListener是观察者,被观察者是控件,当有点击事件的时候控件发布点击事件,观察者OnClickListener就会接收到点击事件。当然了,说到底,就是回调。

    2、用回调写一个简单的观察者模式

    首先我们想一个生活中的场景,来凑个观察者模式出来。平时我们要烧水吧,总要派个人看着,水烧开的时候把电源拔了装水。在这个场景里水壶就是被观察者(Observable),观察者(Observer)就是人。首先我们用以上我说的,那玩意就是回调,用回调来写一个看看。那我们先写一个被观察者“水壶”来看看:

    public class Kettle<T> {
        Observer<T> o;
    
        /**
         * 发布信息
         */
        public void publishEvent(T t){
            if (o == null)
                throw new NullPointerException("you must regist Observer first!");
            notifyData(t);
        }
    
        /**
         * 通知订阅者
         */
        public void notifyData(T t){
            o.receiverEvent(t);
        }
    
        /**
         * 注册一个观察者
         */
        public void registObserver(Observer<T> o){
            this.o = o;
        }
    
        /**
         * 在你需要的时候调用这个方法,防止内存泄露
         */
        public void unregistObserver(){
            this.o = null;
        }
    }
    

    首先我们这个水壶是被观察者,内部肯定要维护一个观察者的引用,或者一个观察者队列的引用,方便我们进行回调,当然更多的事我们尽量不要通过Observer这个东西来做,在这个观察者模式中我希望Observer仅仅作为一个纯粹的回调。因为观察模式本身的特性之一就是解耦,如果你要通过Observer干更多的事无疑会加重Observable和Observer之间的耦合。更多信息可以看代码,我注释已经写得很详细了。

    接下来看看上面提到的那个Observer我是咋写的:

    public interface Observer<T> {
        void receiverEvent(T t);
    }
    

    很简单的一个接口,写上泛型,嗯,顺便练习一下泛型...只有一个方法,用来回调。有接口那我们肯定要有实现类,我这个场景里说了,人是观察者,于是我写了一个Observer的实现类:

    public abstract class People implements Observer<String> {
        @Override
        public void receiverEvent(String s) {
            System.out.println(s);
            dealWithEvent();
        }
    
        /**
         * 交给用户去处理事件
         */
        public abstract void dealWithEvent();
    }
    

    我把这个People设计为一个抽象类,这样我可以在接收到这个事件的时候做一些简单的处理(把他打印出来……),然后再把具体的逻辑交给这个抽象类的子类来做,我这逻辑比较简单,就没传什么参数进去了。最后免不了跑起来看看了~

    public class Test {
        public static void main(String[] args){
            //水壶
            Kettle<String> kettle = new Kettle<>();
            
            People people = new People() {
                @Override
                public void dealWithEvent() {
                    System.out.println("People:拔电源装水了~");
                }
            };
    
            //注册观察者
            kettle.registObserver(people);
            //在一定条件下调用此方法发布事件
            kettle.publishEvent("Kettle:水烧开了!再不拔电源我要炸了!");
        }
    }
    
    运行结果

    这里实现了一个加单的观察者模式,观察者也只能注册一个,不过例子么,简单的才容易看懂嘛~接下来看一下Java util里自带的Observable和Observer,看一下别人的套路~

    3、Java中的观察者模式

    在Java的util包里也有Observable和Observer那么这俩兄弟跟我们上面自己实现的有啥不同呢?首先还是那个水壶的例子,有了上面的基础,我就直接把所有的类和测试代码甩上来了,相信以各位看官的实力都是小case:

    public class HelloWorld {
        public static native String sayHello(String name);
    
        public static void main(String[] args) {
            //被观察者
            Kettle kettle = new Kettle();
            //观察者
            PeopleLookKettle people = new PeopleLookKettle();
    
            kettle.addObserver(people);
            kettle.notifyPeople("kettle:水烧开了!再不拔电源我要炸了!!");
    
        }
    }
    
    public class Kettle extends Observable {
        public void notifyPeople(String str){
            System.out.println("kettle:我是水壶~");
            setChanged();
            notifyObservers(str);
        }
    }
    
    public class PeopleLookKettle implements Observer {
        @Override
        public void update(Observable o, Object arg) {
            System.out.println((String) arg);
            System.out.println("People:拔电源装水~");
        }
    }
    

    之后还是看一下运行结果

    运行结果

    代码上完了,那么这里实现的观察模式又是个什么套路呢?不比比直接看源码,先从简单的Observer看起:

    public interface Observer {
        /**
         * This method is called whenever the observed object is changed. An
         * application calls an <tt>Observable</tt> object's
         * <code>notifyObservers</code> method to have all the object's
         * observers notified of the change.
         *
         * @param   o     the observable object.
         * @param   arg   an argument passed to the <code>notifyObservers</code>
         *                 method.
         */
        void update(Observable o, Object arg);
    }
    

    我去,这跟我上面的设计不符啊...这货怎么把Observable传过来了...这只是设计类和接口的一些设计理念不一样,作为jdk他需要考虑各种兼容性和安全性的问题,所以不可能像我们客户端程序员一样,很多时候写的都非常任性。先不扯那么多,我们要看的是套路~这玩意是个接口,就像我说的那样,做个回调就行了,剩下的都交给实现类来操心。

    看完了Observer我们再来看看Observable:

    public class Observable {
        private boolean changed = false;
        private Vector<Observer> obs;
    
        /** Construct an Observable with zero Observers. */
    
        public Observable() {
            obs = new Vector<>();
        }
    
        /**
         * Adds an observer to the set of observers for this object, provided
         * that it is not the same as some observer already in the set.
         * The order in which notifications will be delivered to multiple
         * observers is not specified. See the class comment.
         *
         * @param   o   an observer to be added.
         * @throws NullPointerException   if the parameter o is null.
         */
        public synchronized void addObserver(Observer o) {
            if (o == null)
                throw new NullPointerException();
            if (!obs.contains(o)) {
                obs.addElement(o);
            }
        }
    
        /**
         * Deletes an observer from the set of observers of this object.
         * Passing <CODE>null</CODE> to this method will have no effect.
         * @param   o   the observer to be deleted.
         */
        public synchronized void deleteObserver(Observer o) {
            obs.removeElement(o);
        }
    
        /**
         * If this object has changed, as indicated by the
         * <code>hasChanged</code> method, then notify all of its observers
         * and then call the <code>clearChanged</code> method to
         * indicate that this object has no longer changed.
         * <p>
         * Each observer has its <code>update</code> method called with two
         * arguments: this observable object and <code>null</code>. In other
         * words, this method is equivalent to:
         * <blockquote><tt>
         * notifyObservers(null)</tt></blockquote>
         *
         * @see     java.util.Observable#clearChanged()
         * @see     java.util.Observable#hasChanged()
         * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
         */
        public void notifyObservers() {
            notifyObservers(null);
        }
    
        /**
         * If this object has changed, as indicated by the
         * <code>hasChanged</code> method, then notify all of its observers
         * and then call the <code>clearChanged</code> method to indicate
         * that this object has no longer changed.
         * <p>
         * Each observer has its <code>update</code> method called with two
         * arguments: this observable object and the <code>arg</code> argument.
         *
         * @param   arg   any object.
         * @see     java.util.Observable#clearChanged()
         * @see     java.util.Observable#hasChanged()
         * @see     java.util.Observer#update(java.util.Observable, java.lang.Object)
         */
        public void notifyObservers(Object arg) {
            /*
             * a temporary array buffer, used as a snapshot of the state of
             * current Observers.
             */
            Object[] arrLocal;
    
            synchronized (this) {
                /* We don't want the Observer doing callbacks into
                 * arbitrary code while holding its own Monitor.
                 * The code where we extract each Observable from
                 * the Vector and store the state of the Observer
                 * needs synchronization, but notifying observers
                 * does not (should not).  The worst result of any
                 * potential race-condition here is that:
                 * 1) a newly-added Observer will miss a
                 *   notification in progress
                 * 2) a recently unregistered Observer will be
                 *   wrongly notified when it doesn't care
                 */
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    
        /**
         * Clears the observer list so that this object no longer has any observers.
         */
        public synchronized void deleteObservers() {
            obs.removeAllElements();
        }
    
        /**
         * Marks this <tt>Observable</tt> object as having been changed; the
         * <tt>hasChanged</tt> method will now return <tt>true</tt>.
         */
        protected synchronized void setChanged() {
            changed = true;
        }
    
        /**
         * Indicates that this object has no longer changed, or that it has
         * already notified all of its observers of its most recent change,
         * so that the <tt>hasChanged</tt> method will now return <tt>false</tt>.
         * This method is called automatically by the
         * <code>notifyObservers</code> methods.
         *
         * @see     java.util.Observable#notifyObservers()
         * @see     java.util.Observable#notifyObservers(java.lang.Object)
         */
        protected synchronized void clearChanged() {
            changed = false;
        }
    
        /**
         * Tests if this object has changed.
         *
         * @return  <code>true</code> if and only if the <code>setChanged</code>
         *          method has been called more recently than the
         *          <code>clearChanged</code> method on this object;
         *          <code>false</code> otherwise.
         * @see     java.util.Observable#clearChanged()
         * @see     java.util.Observable#setChanged()
         */
        public synchronized boolean hasChanged() {
            return changed;
        }
    
        /**
         * Returns the number of observers of this <tt>Observable</tt> object.
         *
         * @return  the number of observers of this object.
         */
        public synchronized int countObservers() {
            return obs.size();
        }
    }
    

    可以看出Observable内部使用了一个Vector来维护订阅的Observer,关于Vector这里不做更多的了解,在这就把他当做一个普通的Observer容器就行了。让我们看看和这个容器有关的套路,为了防止各位产生代码疲劳,我特意贴心的给各位截了个图23333333:

    套路

    添加和删除Observer就是在容器obs里做增删操作,这套路很简单,不过为了线程安全加了个synchronized。之后看一下重点,通知Observers时调用的notifyObservers(),notifyObservers()最终会调用他自身带参的重载方法,看下代码:

        public void notifyObservers(Object arg) {
            /*
             * a temporary array buffer, used as a snapshot of the state of
             * current Observers.
             */
            Object[] arrLocal;
    
            synchronized (this) {
                /* We don't want the Observer doing callbacks into
                 * arbitrary code while holding its own Monitor.
                 * The code where we extract each Observable from
                 * the Vector and store the state of the Observer
                 * needs synchronization, but notifying observers
                 * does not (should not).  The worst result of any
                 * potential race-condition here is that:
                 * 1) a newly-added Observer will miss a
                 *   notification in progress
                 * 2) a recently unregistered Observer will be
                 *   wrongly notified when it doesn't care
                 */
                if (!changed)
                    return;
                arrLocal = obs.toArray();
                clearChanged();
            }
    
            for (int i = arrLocal.length-1; i>=0; i--)
                ((Observer)arrLocal[i]).update(this, arg);
        }
    

    先除去那大段的注释不看,这里就是拿到Observable内部维护的Observer容器,然后遍历回调这些Observer的update方法以实现让所有Observer收到通知。但是后面那段代码被执行是有条件的,就是Observable内部的changed字段为true才会执行,而这个字段只有通过setChanged()方法来将其值置为true。但是在上面的源码中我们可以发现这个方法是protected修饰的,所以不通过特殊手段的话,我们只有通过继承才能来调用这个方法了。所以在我以上的实现代码中是有一个继承于Observable的类的。

    回头再看看那段注释(自己的烂翻译...有错请指出...):
    我们不想让Observer在持有他自己的监听时在回调任意代码。抽取这段代码存储存储Observer需要同步的状态,但是并不通知这些Observer。任何潜在的竞争条件可能会导致的最坏情况是:

    • 新添加的Observer将会错过一个正在进行的通知
    • 最近被解除注册的Observer可能会错误的同步一个他不关心的玩意

    这些东西说实话,我只是有一点点想法,并不能说的很清楚。我觉得是多线程情况下这段代码需要加上一个同步锁,不然可能会引发他注释里写的那两点糟糕的情况。我接触的多线程还是有点少的,所以这段我就先这么翻着,而且这对我们理解观察者模式的套路并没有非常大的影响。

    分析完了Java中的观察者模式,接下来回到文章最前面提到的那个问题,RecyclerView中的ui更新到底是咋回事。

    4、RecyclerView中的ui更新

    其实要弄清楚这个首先得看Adapter,因为Adapter才是掌控数据集的那个。那么让我们来看一下RecyclerView.Adapter的nonotifyDataSetChanged()方法

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

    继续追踪这个源码,看看咋回事

        static class AdapterDataObservable extends Observable<AdapterDataObserver> {
            public boolean hasObservers() {
                return !mObservers.isEmpty();
            }
    
            public void notifyChanged() {
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged();
                }
            }
    
            //其余方法省略
        }
    

    可以看到notifyChanged()这个方法里就两行代码,经过上面的一番学习,我闭着眼睛也能猜到mObservers是一个Observer的集合,通过遍历的去调用onChanged,然后这个onChanged是回调。既然知道这一点,那么我们就需要在RecyclerView中找到Observer的具体实现类,不过在此之前我们找孩子之前得先找他爸~很简单,追踪onChanged()的源码,看看到底是谁的方法。

        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
            }
        }
    

    找到了,都是空方法,你可能回说这还不是接口,没事,接口能做的抽象类也能做,我们只要找这个抽象类的孩子就行了。鸡贼的我果断ctrl+f输入了我的查找:

    嘿嘿嘿,我好鸡贼 说了我很鸡贼

    看一下他的源码:

        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();
                }
            }
        
        //省略部分代码...
    
            void triggerUpdateProcessor() {
                if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) {
                    ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
                } else {
                    mAdapterUpdateDuringMeasure = true;
                    requestLayout();
                }
            }
        }
    

    这里简单的看一下第一行代码是检查recyclerview,如果有错就会抛异常。之后第一个if,不看了,默认是false,那么就看第二个条件内的代码。第一个是存储一个状态,第二个是我们要看到的东西了,点进去看下源码:

        private void setDataSetChangedAfterLayout() {
            //省略部分代码
            mDataSetHasChangedAfterLayout = true;
            final int childCount = mChildHelper.getUnfilteredChildCount();
            for (int i = 0; i < childCount; i++) {
                final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
                if (holder != null && !holder.shouldIgnore()) {
                    holder.addFlags(ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN);
                }
            }
            mRecycler.setAdapterPositionsAsUnknown();
        }
    

    可以看到它会遍历ViewHolder,然后给holder添加flag:** FLAG_ADAPTER_POSITION_UNKNOWN**这个flag会让viewholder重新绑定到recyclerview上以确定自己的position,最后一个方法会让缓存的viewholder也打上上面提到的flag。

    最后再回顾一下(如果有纰漏敬请指出,因为我这源码也没非常仔细的阅读):

    • 在想要更新RecyclerView的界面时,我们通常会先更新数据源(List之类的),然后调用Adapter的notifyDataSetChanged()方法

    • 在RecyclerView内部notifyDataSetChanged()方法调用了mObservable.notifyChanged();而mObservable是一个被观察者。

    • 在RecyclerView内部找到mObservable的真实类型,发现是RecyclerViewDataObserver,寻找notifyChanged()时会调用的onChanged()方法。

    • 发现onChanged()方法最终会给viewholder设置flag,让他们重新绑定到RecyclerView上,在重新绑定的过程中无疑是会在onBindViewHolder里重新设置数据的,而数据源我们已经更新过了,新的数据就会被显示到界面上,以上就是这整个流程了。

    5、最后的一点思考

    说实话最近在写东西的时候经常用回调,因为一些工具类或者dialog、window之类的,在自己自定义的时候通常需要回调把点击事件传出来,不然感觉传view设置点击什么的感觉也挺麻烦的,不如我里面逻辑处理好,就把点击事件传出来就好了。但是写到后面我这又是用的MVP,activity里各种回调满天飞,不过怎么说呢,我自己写的,我看起来逻辑还是很清晰的。如果是后面来人接手呢?虽然我注释写的都很清晰了,但是他在阅读代码的时候不得不深入我的工具类或者dialog window里去看我这个回调到底干了什么,所以这种方便自己麻烦别人的东西,我现在在想到底是算好的代码风格还是差的,有点糊涂。

    好了,也挺久没发文了,而且干了七天,相信大家都很累了,休息休息了,祝各位有一个好的周末。

    最后安利一下这个主播,小缘,安静听歌的感觉不错

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

    相关文章

      网友评论

      • 囚怀:为什么抽象类people可以new?
        囚怀:@xiasuhuei321 这样,谢谢😜
        xiasuhuei321:```
        People people = new People() {
        @Override
        public void dealWithEvent() {
        System.out.println("People:拔电源装水了~");
        }
        };
        ```
        这种写法是匿名类+多态,左边声明的是父类型,右边是一个实现了抽象方法的子类对象。
      • CalvinNing:今天又看了一遍,最近看设计模式着实看的头晕,观察者模式我一直就理解成接口回调。谁要是问我用过没有,我就怼他onClick谁没写过:grin: ,除此之外工厂模式、builder模式也只能理解是个什么套路,至于这个套路必要吗?增加了很多代码量啊。看了很多知乎的回答,不得不说很多人自己都没整明白却再给别人释疑,姑且暂不纠结了。就像楼主开头写的,代码量不足谈设计模式就是耍流氓了。楼主棒棒哒。说了这么多,最后说一句,最后安利的主播链接404了,你个骗子!!!
        xiasuhuei321:当代码量够的时候就隐约会有点感受了,很多时候套路是因为没有更好的选择了……很有可能是语言特性不足,只能用自己的套路来补足。
      • junerver:私以为好的代码风格应该是方便后来者阅读,只是自己理解并觉得方便的话,在团体项目里还是会有些不便吧。
      • Leslie_zhou:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • 23375930c51d:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • mancos:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • 超兽:新注册帐号的第一个评论
      • weigh_tang:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • Z_chao:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • CalvinNing:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • 敌军不是十五万:KFC出品,必属精品。马上加入跟着大佬学Android豪华套餐系列~
      • 木月新痕:围观大佬
      • Gakki的伍记:活捉一只野生精灵!
        xiasuhuei321:@伍记麻辣油 我去,你是哪个大兄弟
      • 忍辞:已阅 :wink:

      本文标题:Android之观察者模式

      本文链接:https://www.haomeiwen.com/subject/gqynyttx.html