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场景的接口方法。

ListAdapter的类图

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 {

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是所有订阅中心的模板类,它为提供不同的模板策略提供了抽象的实现。

public abstract class Observable {

protected final ArrayList mObservers = new ArrayList();

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 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 findSubscriberMethods(Class> subscriberClass) {

// STEP1:查找缓存,若有直接返回

String key = subscriberClass.getName();

List subscriberMethods;

synchronized (methodCache) {

subscriberMethods = methodCache.get(key);

}

if (subscriberMethods != null) {

return subscriberMethods;

}

subscriberMethods = new ArrayList();

Class> clazz = subscriberClass;

HashSet eventTypesFound = new HashSet();

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 subscriptions = subscriptionsByEventType.get(eventType);

Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);

// STEP1:准备容器

if (subscriptions == null) {

subscriptions = new CopyOnWriteArrayList();

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> subscribedEvents = typesBySubscriber.get(subscriber);

if (subscribedEvents == null) {

subscribedEvents = new ArrayList>();

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 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 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> 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 终章

无论项目的大小、复杂度如何,观察者的主线索其实一直很清晰——发布/订阅,这对于我们理解它真的很重要。

观察者模式的本质:触发联动。

android观察者模式容器联动,观察者模式(触发联动)相关推荐

  1. 对象间的联动——观察者模式

    本文转载自 :http://blog.csdn.net/lovelion/article/details/7720232 观察者模式是设计模式中的"超级模式",其应用随处可见,在之 ...

  2. Android 开发中的观察者模式应用实例

    前言 最近在遇到了 Android 的开发中常用到的设计模式之观察者模式,观察者模式,所谓的模式就是一种设计思想,可以按照某种模式,写出更合理,简单,有效的代码.可以用在 Android 开发中,也可 ...

  3. Android开发模式之观察者模式

    目录 一.定义 1.观察者模式 2.UML类图 3.观察者模式中的角色 二.使用场景 三.简单实现 四.观察者模式在java.util包中的应用 五.观察者模式在Button中的应用 六.观察者模式在 ...

  4. android三级联动、四级联动(地区选择)

    GitHub地址:https://github.com/gamekonglee/RegionSelector 先上效果图: allprojects { repositories { .. maven ...

  5. java 观察者模式讲解_java观察者模式详解

    简单地说,观察者模式定义了一个一对多的依赖关系,让一个或多个观察者对象监察一个主题对象.这样一个主题对象在状态上的变化能够通知所有的依赖于此对象的那些观察者对象,使这些观察者对象能够自动更新. 观察者 ...

  6. html中css二级联动,html二级联动学习笔记

    DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> http://www.cnblogs.com/whg ...

  7. Android时间触发,设置闹钟管理器Android中的时间 - 立即触发闹钟

    这里我试图通过使用AlarmManger类设置闹钟.它与我工作正常,但当我设置闹钟时间后小时或分钟时间选择器,它会立即启动时,我保存该报警.闹钟.我需要警报熄灭,直到我设定时间. 下面是我的代码正在工 ...

  8. 如何使用JQuery实现Js二级联动和三级联动

    前言:使用JQuery封装好的js方法来实现二级三级联动要比直接使用js来实现二级三级联动要简洁很多.所以说JQuery是个非常强大的.简单易用的.兼容性好的JavaScript库,已经成为前端开发人 ...

  9. Android实现导航菜单随着ListView联动,当导航菜单遇到顶部菜单时停止在哪里,并且listview仍能滑动...

    需求:现要实现一个特殊UI的处理,如下图所示: 该布局的上面是一个"按钮",中间是一个"空白布局(当然也可以是ViewPager等)",下面是一个页面的导航菜单 ...

最新文章

  1. js 判断手机横竖屏的实现方法(不依赖任何其他库)
  2. 参考文献找不全页码?
  3. Ubuntu12.10-amd64系统上搭建Android4.2(JellyBean)源码开发环境
  4. 【WEB HTTP】缓存
  5. 上几个WebAPI就算微服务架构?Too Young!
  6. 传递函数_使用python计算麦克风阵列信号的传递函数
  7. 后序线索树怎样画图_算法新解刘新宇(二)二叉搜索树:数据结构中的“hello world”...
  8. 基于mono和C#运行的cms产品
  9. Flex 4命名空间
  10. String,StringBuffer,StringBuilder效率优先关系说明
  11. zabbix4.0添加mysql报警_Zabbix 3.4.3实现企业微信报警
  12. 【原】Coursera—Andrew Ng机器学习—Week 9 习题—异常检测
  13. 联想服务器修改imm地址,联想服务器IMM运维管理指南.docx
  14. Ps中 不透明度和填充的区别
  15. 【SQL】实验十 数据库完整性实验
  16. 字节架构师发布“绝版”Java并发实现原理剖析JDK源码
  17. 按钮英文字母大小写-默认样式修改-vuetify
  18. 浏览器和服务器的区别
  19. python读excel中的sheet
  20. 酷睿i5 12490f什么水平 i5 12490f属于什么档次 i512490f怎么样

热门文章

  1. SQL Serve Intersect(交集)
  2. 电子档案管理系统(ssm,mysql)
  3. hadoop的find
  4. RabbitMQ原理构成分析以及实战应用
  5. RabbitMQ 原理图和名词理解(二)
  6. 基于java电影院购票售票系统设计与实现
  7. Stata: 蒙特卡洛模拟(Monte Carlo Simulation)没那么神秘
  8. Java泛型中的T代表什么
  9. ios开发怎么接入面容id_Flutter混合开发
  10. 微信公众平台接入之简单任务分发器