阅读 651

Android 中的设计模式:观察者模式

前言

观察者模式是我们开发工作中经常使用的开发模式。Android 源码中也有很多地方用到此模式。比如:ListView、ContentProvider 和 Broadcast 等等。本文将会介绍观察者模式、实现一个观察者模式并结合 Android 源码进行分析。

定义

定义对象间的一种一个对多的依赖关系,当一个对象的状态发送改变时,所以依赖于它的对象都得到通知并被自动更新。

介绍

观察者模式又被称作发布/订阅模式。即 1 人发布,N 人订阅。观察者模式主要用来解耦,将被观察者和观察者解耦,让他们之间者依赖关系很小,甚至不存在依赖。它最用的地方是 GUI 系统、订阅——发布系统。举个例子,我司开发的一个 App 左上角状态栏需要实时显示闹钟的状态,若存在设定好的闹钟则显示闹钟图标,否则不显示。因为存储闹钟使用的 ContentProvider,所以可以使用一个监听器监听闹钟数据是否发生变化,若发生变化就判断当前闹钟状态并显示或关闭图标。这样便实现了实时显示闹钟状态的功能。

UML 类图

手工实现

  • 创建观察者

实现抽象观察者 Observer 中的方法,这里创建一个 Coder 类,并定义收到通知后的动作:

/**
 * 程序员是观察者
 *
 */
public class Coder implements Observer{
	public String name;
	
	public Coder(String mName) {
		name = mName;
	}

	@Override
	public void update(Observable arg0, Object arg1) {
		// TODO Auto-generated method stub
		System.out.println("嘿," + name + ",您的快递到了");
	}
	
	public String toString(String name) {
		return "程序员:"+name;
	}
}
复制代码
  • 创建被观察者 实现 Observable 方法,也就是快递员,快递到了会通知程序员们来拿快递:
/**
 * Courier 即快递员,当他到公司时通知所有观察者(有快递的程序员)拿快递
 * @author Rickon
 *
 */
public class Courier extends Observable {
	public void postNewExpress(String content) {
		//标识状态或者内容发生改变(此处为快递到了)
		setChanged();
		
		//通知所有观察者
		notifyObservers(content);
	}
}
复制代码
  • 测试
public class Test {

	public static void main(String[] args) {
		//被观察者
		Courier courier = new Courier();
		//观察者
		Coder coder1 = new Coder("程序员张三");
		Coder coder2 = new Coder("程序员李四");
		Coder coder3 = new Coder("程序员王二麻子");
		
		//将观察者注册到被观察者的观察者列表中
		courier.addObserver(coder1);
		courier.addObserver(coder2);
		courier.addObserver(coder3);
		
		//快递到了
		courier.postNewExpress("快递到啦!");
	}

}
复制代码
  • 输出结果
嘿,程序员王二麻子,您的快递到了
嘿,程序员李四,您的快递到了
嘿,程序员张三,您的快递到了
复制代码

总结 上述代码就实现了一个简单地观察者模式,使用的是 JDK 内部内置的 Observable(抽象被观察者),Observer(抽象观察者)。JDK 内置观察者模式也说明了观察者模式应用的广泛与重要性。

应用场景

  • 事件多级触发场景;
  • 当一个对象必须通知别的对象,而它又不能假定对象是谁;
  • 跨系统的消息交换场景,如消息队列、事件总线的处理机制。

优缺点

优点

  • 观察者与被观察者之间是抽象耦合,可以很好地应对业务变化;
  • 增强系统灵活性、可扩展性。

缺点

  • 开发过程中可能会出现一个被观察者、多个观察者的情况,开发和调试会变得比较复杂。除此之外,观察者太多的话收到通知需要消耗更长的时间。

Android 中的观察者模式

  • ListView 深入解析
    ListView 是 Android 中很常用的控件,我们在使用 ListView 添加数据后会调用 Adapter 的 notifyDataSetChanged()方法,这是为啥呢?下面我们来一探究竟!

我们发现 notifyDataSetChanged,这个方法是在 BaseAdapter 中定义的,具体带么如下:

public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
    private final DataSetObservable mDataSetObservable = new DataSetObservable();

    public boolean hasStableIds() {
        return false;
    }
    
    public void registerDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.registerObserver(observer);
    }

    public void unregisterDataSetObserver(DataSetObserver observer) {
        mDataSetObservable.unregisterObserver(observer);
    }
    
    /**
     * 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();
    }

    //省略部分代码
}
复制代码

BaseAdapter 看起来是观察者模式,那么到底 BaseAdapter 是怎么运行的呢?又是如何实现的观察者模式,让我们继续分析。

首先看看 mDataSetObservable.notifyChanged() 方法:

public class DataSetObservable extends Observable<DataSetObserver> {
    /**
     * Invokes {@link DataSetObserver#onChanged} on each observer.
     * Called when the contents of the data set have changed.  The recipient
     * will obtain the new contents the next time it queries the data set.
     */
    public void notifyChanged() {
        synchronized(mObservers) {
            // 遍历调用每个观察者的 onChanged() 方法来通知被观察者发生变化
            for (int i = mObservers.size() - 1; i >= 0; i--) {
                mObservers.get(i).onChanged();
            }
        }
    }
    
    //省略部分代码
}

复制代码

这里很容易看到此方法是遍历所有观察者,并且调用所有观察者的 onChnged() 方法,从而告知观察者发生了变化。那么这些观察者是怎么设置的呢?其实这些观察者就是 ListVIew 通过 setAdapter 方法产生的,我们看看这部分代码:

 @Override
    public void setAdapter(ListAdapter adapter) {
        //如果已经有了 Adapter,那么先注销 Adapter 对应的观察者
        if (mAdapter != null && mDataSetObserver != null) {
            mAdapter.unregisterDataSetObserver(mDataSetObserver);
        }

        //省略部分代码
        super.setAdapter(adapter);

        if (mAdapter != null) {
            mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
            mOldItemCount = mItemCount;
            //获取数据的数量
            mItemCount = mAdapter.getCount();
            checkFocus();
            //此处创建一个个数据集观察者
            mDataSetObserver = new AdapterDataSetObserver();
            //将观察者注册到 Adapter 中,实际上是注册到 DatasetObservable
            mAdapter.registerDataSetObserver(mDataSetObserver);

            //省略部分代码
        } else {
           //省略部分代码
        }

        requestLayout();
    }
复制代码

从上述代码中可以看出,在设置 Adapter 时会构建一个 AdapterDataSetObserver,也就是观察者,之后再将这个观察者注册到 Adapter 中,这样我们的被观察者和观察者就都构建好了。那么 AdapterDataSetObserver 到底是怎么运行的呢?它是定义在 ListView 的父类 AbsListView 中,代码如下:

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();
            }
        }
    }
复制代码

它又继承自 AbsListView 的父类 AdapterView 的 AdapterDataSetObserver,代码如下

class AdapterDataSetObserver extends DataSetObserver {

        private Parcelable mInstanceState = null;
        //前面提到的调用 Adapter 的 notifyDataSetChanged 时会调用所有观察者的 onChanged 方法,核心实现代码就在这里
        @Override
        public void onChanged() {
            mDataChanged = true;
            mOldItemCount = mItemCount;
            //获取 Adapter 中数据的数量
            mItemCount = getAdapter().getCount();

            // Detect the case where a cursor that was previously invalidated has
            // been repopulated with new data.
            if (AdapterView.this.getAdapter().hasStableIds() && mInstanceState != null
                    && mOldItemCount == 0 && mItemCount > 0) {
                AdapterView.this.onRestoreInstanceState(mInstanceState);
                mInstanceState = null;
            } else {
                rememberSyncState();
            }
            checkFocus();
            //刷新 ListView、GridView 等 AdapterView 等组件
            requestLayout();
        }
        
        //省略部分代码
    }
复制代码

源码分析到这里就很明显了,当我们调用 Adapter 的 notifyDataSetChanged 方法,这个方法又会调用 DataSetObservable 的 notifyDataSetChanged 方法,而这个方法会调用所有观察者的 onChanged 方法,在 onChanged 方法中则是会调用 ListView 的重新布局方法刷新UI,这就是一个完整的观察者模式。

ListView 观察者模式实现总结: 我重新概述这一过程:Adapter 中包含一个数据集可观察者 DataSetObservable,在数据数量发生变更时,开发者手动调用 Adapter.notifyDataSetChanged,实际上调用的则是 DataSetObservable 的 onChanged 方法,该方法会遍历所有观察者的 onChanged 方法。在 AdapterDataSetObserver 的 onChanged 函数中会获取 Adapter 中数据集的新数量,然后调用 ListView 的 requestLayout() 方法重新布局并更新 UI。

  • ContentProvider 文章的开始有提到使用 ContentProvider 的过程中也用到了观察者模式。首先创建一个观察者对象,代码如下:
ContentObserver alarmObserver = new ContentObserver(new Handler()) {
        //创建观察者对象
        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            //执行业务代码
        }

        @Override
        public void onChange(boolean selfChange, Uri uri) {
            super.onChange(selfChange, uri);
        }
    };
复制代码

注册观察者:

mContext.getContentResolver().registerContentObserver(url, true, alarmObserver);
复制代码

看到此注册方法有三个参数分别代表什么意义呢:

  • uri:针对对有变化的感兴趣进行监听的URI;
  • notifyForDescendents:true表示以uri前缀开始的任何变化都进行通知;false表示完全匹配才进行通知;
  • observer:IContentObserver接口,提供了一个方法onChange,变化发生时Cursor需要更新时调用

这样就通过指定Uri可以仅对数据库中感兴趣的数据有变化时,进行监听。具体源码就不再进行分析,大家感兴趣可以自行去分析源码,观察者模式是万变不离其宗的。