观察者模式(触发联动)
0、提纲
目录:
1、举例:发起登录请求
2、Android Adapter 相关源代码分析
3、EventBus 相关源代码分析
4、观察者模式总结
需要查看其它设计模式描述可以查看我的文章《设计模式开篇》。
1、举例:发起登录请求
现在假设有登录接口(login),需要传入参数(username、password)。众所周知的是网络请求本身是耗时操作,并且 android 不允许在UI 线程发起网络请求。
所以我们会另开辟线程去执行登录操作,代码看起来像下面:
// Thread.java
public class LoginThread extends Thread{
public void run(){
Result result = api.login("username","password");
}
}
我们已经获得了登录的结果Result,但是怎样才能将 Result 告知给客户端调用者呢?
方案1:设置回调接口
通过将ICallback
的实例传给 Thread 对象,这样当 Thread 对象内部获取到 Result 实例时即可将结果回调出去。
public interface ICallback{
void onCallback(Result result);
}
方案2:应用观察者模式
再获取到Result 时,向发布订阅中心发送一条通知观察者的事件。由发布订阅中心将事件(依据某种规则)发送给订阅者。它与采用回调的方式相比最显著的区别是:回调只能针对单个对象进行,而观察者可以通过观察者中心触发多个观察者对象联动。
观察者的行为其实也很好理解,整个过程可以划分为4个部分:
1、向注册中心注册(向花店订购了每周一束花的套餐)
2、外部发送事件(每天送花人都会将花送到花店)
3、获取订阅对象(花店老板检查到你本周的花还没有配送,于是将你列入待配送的清单)
4、通知订阅对象(由送花员将花束送到你的家里)
理论的东西不讲太多,下面我们结合源代码进行分析。
2、Android Adapter 相关源代码分析
2.1、Adapter
我们都知道 Adapter 意味着数据源,往往数据源的改动会影响着 UI 的变更。所以对 Adapter 的分析自然是我们第一步要做的事情。
public interface Adapter {
void registerDataSetObserver(DataSetObserver observer);
void unregisterDataSetObserver(DataSetObserver observer);
// 省略其他代码
}
在Adapter
的接口规范中就已经定义了注册与反注册DataSetObserver
实例的方法。注册DataSetObserver
的目的,是为了在适配器内的数据发生更改时进行调用。
2.2、DataSetObserver
DataSetObserver
是数据观察员,它的代码定义如下。它关注两个维度的数据变更,数据发生改变 或者 数据失效。
public abstract class DataSetObserver {
public void onChanged() {
// Do nothing
}
public void onInvalidated() {
// Do nothing
}
}
2.3、ListAdapter
因为我们并不打算脱离ListView
分析抽象的玩意,所以我们回归到实例ListAdapter
。让我们看看ListAdapter
的实现,ListAdapter
继承了接口Adapter
并扩充一些适用于List
场景的接口方法。
2.4、BaseAdapter
由于ListAdapter
是接口,所以我们仍需查找实现了该接口的类——即BaseAdapter
。
我们看到了DataSetObservable
是被观察的对象,是真正触发观察者对象联动的源头。
public abstract class BaseAdapter implements ListAdapter, SpinnerAdapter {
// 定义了被观察的对象,即只要这个对象发生变更。那么订阅它的对象,都有机会触发行为。
private final DataSetObservable mDataSetObservable = new DataSetObservable();
public void registerDataSetObserver(DataSetObserver observer) {
// 将订阅者注册
mDataSetObservable.registerObserver(observer);
}
public void unregisterDataSetObserver(DataSetObserver observer) {
// 取消注册
mDataSetObservable.unregisterObserver(observer);
}
public void notifyDataSetChanged() {
// 通知订阅者数据已变更
mDataSetObservable.notifyChanged();
}
public void notifyDataSetInvalidated() {
// 通知订阅者数据已失效
mDataSetObservable.notifyInvalidated();
}
}
2.5、DataSetObservable
你可以将DataSetObservable
理解为向订阅对象触发行为的实现,它们可以选择向谁发送、发送什么样的事件。比如:DataSetObservable
就是遍历所有的订阅者并向他们推送信息。
public class DataSetObservable extends Observable<DataSetObserver> {
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();
}
}
}
}
2.6、Observable
Observable<T>
是所有订阅中心的模板类,它为提供不同的模板策略提供了抽象的实现。
public abstract class Observable<T> {
protected final ArrayList<T> mObservers = new ArrayList<T>();
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();
}
}
}
2.7、总结
观察者模式(又称发布/订阅模式)相比享元或解释器等模式,它的模式实现逻辑非常清晰。
有的同学可能对observer (订阅者)与 observable(可供订阅的对象)
这两个词分不清楚,建议结合上文中贴出的图再加以思考,应该可以理解它们的差异。
3、EventBus 相关源代码分析
在分析之前你要先对 EventBus 有些了解,如果还不知道可以查看EventBus。
// 1、注册监听
EventBus.getDefault().register(this);
// 2、接收事件
public void onEvent(Event event) {
// 省略细节代码
}
// 3、取消注册监听,防止内存泄露
EventBus.getDefault().unregister(this);
3.1、注册&反注册
register(this)
目的,是为了将自身句柄注册到发布订阅中心中,以便发布订阅中心向this
发送事件。unregister(this)
的目的是为了避免内存泄露。
3.2、register()
// EventBus.java
public void register(Object subscriber) {
register(subscriber, false, 0);
}
private synchronized void register(Object subscriber, boolean sticky, int priority) {
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod, sticky, priority);
}
}
1、register
方法有三个参数:订阅对象,是否粘性,优先级。
2、再调用注册方法时,首先会调用subscriberMethodFinder.findSubscriberMethods
查找订阅对象中的订阅方法(即回调函数)
3、然后再依次使用回调函数执行订阅。
所以EventBus
真正的订阅对象是回调函数。
3.3、SubscriberMethodFinder
// SubscriberMethodFinder.java
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
// STEP1:查找缓存,若有直接返回
String key = subscriberClass.getName();
List<SubscriberMethod> subscriberMethods;
synchronized (methodCache) {
subscriberMethods = methodCache.get(key);
}
if (subscriberMethods != null) {
return subscriberMethods;
}
subscriberMethods = new ArrayList<SubscriberMethod>();
Class<?> clazz = subscriberClass;
HashSet<String> eventTypesFound = new HashSet<String>();
StringBuilder methodKeyBuilder = new StringBuilder();
while (clazz != null) {
String name = clazz.getName();
// STEP2:跳过系统类,有助于提高性能
if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
break;
}
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
String methodName = method.getName();
// STEP3 :筛选出满足约定的订阅函数
if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
ThreadMode threadMode;
if (modifierString.length() == 0) {
threadMode = ThreadMode.PostThread;
} else if (modifierString.equals("MainThread")) {
threadMode = ThreadMode.MainThread;
} else if (modifierString.equals("BackgroundThread")) {
threadMode = ThreadMode.BackgroundThread;
} else if (modifierString.equals("Async")) {
threadMode = ThreadMode.Async;
} else {
if (skipMethodVerificationForClasses.containsKey(clazz)) {
continue;
} else {
throw new EventBusException("Illegal onEvent method, check for typos: " + method);
}
}
Class<?> eventType = parameterTypes[0];
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(methodName);
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
if (eventTypesFound.add(methodKey)) {
// Only add if not already found in a sub class
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}
}
} else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
+ methodName);
}
}
}
clazz = clazz.getSuperclass();
}
clazz = clazz.getSuperclass();
}
// STEP4:加入缓存
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
+ ON_EVENT_METHOD_NAME);
} else {
synchronized (methodCache) {
methodCache.put(key, subscriberMethods);
}
return subscriberMethods;
}
}
这段方法有点长但结构却比较清晰,大概描述下面几件事情:
1、检查缓存,若有则命中如无则往下执行。
2、检查是否是系统类,若是直接跳过。
3、检查是否以onEvent
作为首字符串。
4、检查以onEvent
作为首字符串的字符串后缀(value)。
4.1、value =
""
,则意味着使用ThreadMode.PostThread。
4.2、value="MainThread"
,则意味着使用ThreadMode.MainThread。
4.3、value="BackgroundThread"
,则意味着使用ThreadMode.BackgroundThread。
4.4、value="Async"
,则意味着使用ThreadMode.Async
5、如果到此都没有发现以onEvent
作为首字符串,则会抛出异常。
6、如果检查到以onEvent
作为首字符串,则会缓存结果。
3.4、执行订阅 subscribe
// 必须在同步块中调用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
Class<?> eventType = subscriberMethod.eventType;
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
// STEP1:准备容器
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<Subscription>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
// STEP2:避免重复订阅
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
// 自从 EventBus 2.2 开始我们强制约定方法是 public 权限(或你通过注解的形式改变访问权限)
// subscriberMethod.method.setAccessible(true);
// STEP3:检查优先级,将订阅者放到合适的位置
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
subscriptions.add(i, newSubscription);
break;
}
}
// STEP4:将订阅方法与订阅方法内的参数做映射
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<Class<?>>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
// STEP5:检查是否粘性事件
if (sticky) {
Object stickyEvent;
synchronized (stickyEvents) {
stickyEvent = stickyEvents.get(eventType);
}
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
}
}
}
subscribe
方法的本质是准备好订阅方法-->与订阅方法的参数的映射关系。
// 订阅方法:onEvent
// 订阅方法的参数:Event
public void onEvent(Event event) {
}
3.5、post
到此位置该准备好的都准备好了,接下来就等待外部触发事件了。
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
1、内部准备好List<Object> eventQueue
用作事件的派发。
2、如果事件未派发,则执行派发。并设置eventQueue
的派发状态为isPosting=true
。
3、调用postSingleEvent(eventQueue.remove(0), postingState);
执行事件派发。
4、派发完成后,设置eventQueue
的派发状态为isPosting= false
。
3.6、postSingleEvent
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
// STEP1:检查事件的继承性
if (eventInheritance) {
// STEP2:向上查找事件类型
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
// STEP3:若非继承性则直接派发
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
// STEP4:若未查找到事件,则派发没有订阅该事件
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
3.7、postToSubscription
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case PostThread:
invokeSubscriber(subscription, event);
break;
case MainThread:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BackgroundThread:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case Async:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
依据订阅方法的特性(解析其后缀)能得到需要在哪个线程中去接收该回调。postToSubscription
恰恰是将对应的操作放到对应线程的策略方法。虽然其本身并没有什么神奇之处,但通过层层封装则会将客户端调用简化到足够神奇。
因为线程切换并不是本章的范畴,所以不展开对每个线程的调用分析。当前就以
PostThread
为根据进行后续分析。
3.8、invokeSubscriber
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
我们观察到最终是通过Method
的invoke
方法,完成对订阅方法(onEvent)的调用,并且传入的参数event
。
// Method.java
public native Object invoke(Object receiver, Object... args)
throws IllegalAccessException, IllegalArgumentException, InvocationTargetException;
3.9 EventBus 总结
经过一大长段的代码分析,我们终于到了尾声。
1、EventBus 通过onEventXXX()
的方法或 以@Subscribe
注解形式,约定接收回调的方法。
2、外部调用post()
方法,将参数event
传入到注册中心EventBus
。
3、内部利用反射的 API,利用 Method反射方法method
及参数event
,最终能够调用约定接收回调的方法。
4 终章
无论项目的大小、复杂度如何,观察者的主线索其实一直很清晰——发布/订阅,这对于我们理解它真的很重要。
观察者模式的本质:触发联动。