源码传送门:https://github.com/square/otto最近工作不是很忙,就花了半天的时间阅读了Otto的源码,于是就有了这篇博文,自己写下的心得体会,算是个学习笔记。Otto的原理并不难,其源码阅读起来也很容易,其思想原理就是订阅和发布事件,实质是注解+技术反射而实现的观察者模式,但是呢从其实现设计上来看看是是可以琢磨出一些东西出来。
其使用也很简单,如下所示:
class DataBean {
String data = "hello otto";
}
class BusProvider {
public static final Bus bus = new Bus();
}
class Observer {
public Observer() {
//注册对象
BusProvider.bus.register(this);
}
//消费事件的方法1
@Subscribe
public void update0(DataBean dataBean) {
Log.i("Otto", "接收到的数据0=" + dataBean.data);
}
//消费事件的方法2
@Subscribe
public void update1(DataBean dataBean) {
Log.i("Otto", "接收到的数据1=" + dataBean.data);
}
}
class Observable1 {
public Observable1() {
//注册对象
BusProvider.bus.register(this);
}
//生产事件第一种方式
@Produce
public DataBean notifyObservers() {
return new DataBean();
}
}
class Observable {
public Observable() {
}
//生产事件第二种方式
public void notifyObservers() {
BusProvider.bus.post(new DataBean());
}
}
可以看出如果想要处理Bus生产的事件,则在相关的类的某个或者某些方法上面添加上@Subscribe注解即可,但是需要在该类初始化的时候调用bus对象的register方法。
再说生产事件,Otto生产事件的方式有两种,一种是调用Bus的post方法,发送一个事件;第二种就是用@Produce的方式;无论哪种方式,其主要目的就是发送一个对象(可能是数据对象,也可能是其他对象);然后交给系统中标有@Subsribe的方法接收和处理发送的对象。当然使用注解@Produce的时候也需要先调用register方法。这点和post方法不一样。
下面先说说@Produe和@Subscribe注解是怎么发挥工作的,既然在使用Otto的时候需要添加@Subscribe和@Produce标注(该标注需不需要视情况而定),那么肯定需要某种途径来检索和收集这些注解所标注的方法(也就是Method对象),将收集到的方法用集合存起来。以便在需要的时机从集合中获取和执行这些方法。确切的说用反射的方式执行这些方法,如下:
//@Produce生成事件:在EventProducer类中
Object evnent = produceMethod.invoke(targetObj)
//@Subscribe消耗事件:在EventHandler类中
subScribeMethod.invoke(targetObj, event);
事实上上面的两行代码也就是整个Otto的核心实现原理:produce方法执行后返回一个event对象,然后该对像作为subscribeMehtod的参数去执行。
即软需要检索和收集注解方法,那么肯定有两个行为:一是检索和收集标注了@Produce的方法集合,另一个是检索和收集@Subscrice方法集合。为此Otto对这两个行为抽象了一出了一个接口HandlerFinder:
interface HandlerFinder {
//检索和收集标注了@Producer的method集合
Map<Class<?>, EventProducer> findAllProducers(Object listener);
//检索和收集标注了@Subscriber的method集合
Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener);
}
使用接口的好处就是便于扩展,Otto对此接口提供了默认实现:
HandlerFinder ANNOTATED = new HandlerFinder() {
@Override
public Map<Class<?>, EventProducer> findAllProducers(Object listener) {
return AnnotatedHandlerFinder.findAllProducers(listener);
}
@Override
public Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
return AnnotatedHandlerFinder.findAllSubscribers(listener);
}
};
为了方便客户端使用自己的检索方式,Otto在初始化Bus对象的时候提供了有个重载构造器:
Bus(ThreadEnforcer enforcer, String identifier, HandlerFinder handlerFinder) {
this.enforcer = enforcer;
this.identifier = identifier;
//自定义HandlerFinder
this.handlerFinder = handlerFinder;
}
这也是面向对象设计理念的很好体现,所以说别看Otto的源码很简单,但是其设计理念还是可以学习和体会,并将此理念应用到自己的应用中去。
Otto的主要是一个产生事件,一个消费事件,从单一职责来看这也是两个对象,于是乎就有两个HandlerFinder 接口中的两个对象:EventProducer对象(用来产生事件)和EventHandler对象(用来消费事件)。
上面也说过产生事件和消费事件的核心原理就是method.invoke方法,所以EventProducer和EventHandler的封装的数据就不难理解了:
class EventHandler {
/**register的目标对象 */
private final Object target;
/**标注了@Subscribe的方法 */
private final Method method;
}
class EventProducer {
/**register的目标对象 */
private final Object target;
/**标注了@Produce的方法 */
private final Method method;
}
既然Otto提供了默认实现,那么就看看其是怎么完成注解方法的检索和收集的,以findAllProducers方法为例:
//参数listener为目标对象
static Map<Class<?>, EventProducer> findAllProducers(Object listener) {
//1、获取目标对象的class对象
final Class<?> listenerClass = listener.getClass();
//方法的返回结果集合
Map<Class<?>, EventProducer> handlersInMethod = new HashMap<Class<?>, EventProducer>();
//从缓存中查找:key是@producer的方法返回类型的class,Value为method
Map<Class<?>, Method> methods = PRODUCERS_CACHE.get(listenerClass);
//缓存不存在
if (null == methods) {
methods = new HashMap<Class<?>, Method>();
//2、查找配置了@Producer注解的方法
loadAnnotatedProducerMethods(listenerClass, methods);
}
//3、遍历所有的@procuder方法
if (!methods.isEmpty()) {
for (Map.Entry<Class<?>, Method> e : methods.entrySet()) {
//形成一个EventProducer对象
EventProducer producer = new EventProducer(listener, e.getValue());
handlersInMethod.put(e.getKey(), producer);
}
}
return handlersInMethod;
}
上面的代码逻辑很明白:
1、获取目标对象的class对象
2、通过loadAnnotatedProducerMethods来完成@Produce方法的检索,检索的结果填充到Map《class,Method》集合中。
3、检索完成后,循环步骤2产生的map集合,然后将集合中的一个个method连同目标对象组成一个EventProducer对象,最终放入Map《class,EventProducer》类型的集合中。
需要注意步骤2和步骤3的map的key的类型,其map的key类型是@Produce方法的返回类型的class对象,比如下面两个方法:
class Target1{
@Produce
public A pA(){return new A()}
@Produce
public B pB(){return new B()}
}
class Target2{
@Produce
public C pC(){return new C()}
}
其Key就是A.class和B.class,所以findAllProducers返回的集合中的元素就是如下:
这里写图片描述上面的的表格其实是步骤2的loadAnnotatedProducerMethods检索后交给步骤3形成的,所以看看loadAnnotatedProducerMethods对@Produce是怎么检索的,该方法最终又调用了loadAnnotatedMethods,本方法在此只关注对@Produce的处理:
private static void loadAnnotatedMethods(Class<?> listenerClass,
Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods) {
//循环目标类的方法
for (Method method : listenerClass.getDeclaredMethods()) {
//省略部分代码
//处理标有@Subscribe注解的方法
if (method.isAnnotationPresent(Subscribe.class)) {
//省略
//注解@produce的方法
} else if (method.isAnnotationPresent(Produce.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
//方法不能有参数
if (parameterTypes.length != 0) {
throw new IllegalArgumentException("必须无参数");
}
//必须有返回类型
if (method.getReturnType() == Void.class) {
throw new IllegalArgumentException("必须有返回值");
}
//返回类型不能是接口
Class<?> eventType = method.getReturnType();
if (eventType.isInterface()) {
throw new IllegalArgumentException("返回不能是接口");
}
if (eventType.equals(Void.TYPE)) {
throw new IllegalArgumentException("Method " + method + " has @Produce annotation but has no return type.");
}
//必须public修饰
if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
throw new IllegalArgumentException("必须public修饰");
}
//一个类中同一个返回类型只能有一个方法
if (producerMethods.containsKey(eventType)) {
throw new IllegalArgumentException("一个类中同一个返回类型只能有一个方法");
}
//key为返回类型的class对象,value为方法名
producerMethods.put(eventType, method);
}
}//end for
//缓存起来
PRODUCERS_CACHE.put(listenerClass, producerMethods);
SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);
}
从代码中可见Otto对@Produce方法做了诸多的限制,详情看阅读上述代码,不做赘述。
同理对@Subcribe的检索和收集也很简单,代码还是在loadAnnotatedMethods方法中:
private static void loadAnnotatedMethods(Class<?> listenerClass,
Map<Class<?>, Method> producerMethods, Map<Class<?>, Set<Method>> subscriberMethods) {
//循环遍历目标类的方法
for (Method method : listenerClass.getDeclaredMethods()) {
//处理标有@Subscribe注解的方法
if (method.isAnnotationPresent(Subscribe.class)) {
Class<?>[] parameterTypes = method.getParameterTypes();
//该方法必须含有且只含有一个参数
if (parameterTypes.length != 1) {
throw new IllegalArgumentException("必须含有且只含有一个参数");
}
//该方法的参数不能是接口
Class<?> eventType = parameterTypes[0];
if (eventType.isInterface()) {
throw new IllegalArgumentException("参数不能是接口类型");
}
//该方法必须为public
if ((method.getModifiers() & Modifier.PUBLIC) == 0) {
throw new IllegalArgumentException("该方法必须为public");
}
//注意key为参数的class类型
Set<Method> methods = subscriberMethods.get(eventType);
if (methods == null) {
methods = new HashSet<Method>();
subscriberMethods.put(eventType, methods);
}
methods.add(method);
} else if (method.isAnnotationPresent(Produce.class)) {
}
}//end for
//缓存起来
PRODUCERS_CACHE.put(listenerClass, producerMethods);
SUBSCRIBERS_CACHE.put(listenerClass, subscriberMethods);
}
如果loadAnnotatedMethods检索下面代码的话:
class Target1{
@Subscribe
public subA1(A a){}
@Subscribe
public subA2(A a){}
@Subscribe
public subA3(A a){}
@Subscribe
public subB1(B b){}
@Subscribe
public subB2(B b){}
@Subscribe
public subB3(B b){}
}
最总返回的map集合形如:
这里写图片描述
通过上面的讲解我们知道,HandlerFinder的接口查找@Subsribe的方法返回的Map《class,EventHandler》集合,所以上面的表格所表示的集合通过findAllSubscribers方法处理后形成了如下表格:
static Map<Class<?>, Set<EventHandler>> findAllSubscribers(Object listener) {
Class<?> listenerClass = listener.getClass();
Map<Class<?>, Set<EventHandler>> handlersInMethod = new HashMap<Class<?>, Set<EventHandler>>();
//省略部分代码
if (!methods.isEmpty()) {
//对上面的map表格做循环
for (Map.Entry<Class<?>, Set<Method>> e : methods.entrySet()) {
Set<EventHandler> handlers = new HashSet<EventHandler>();
for (Method m : e.getValue()) {
handlers.add(new EventHandler(listener, m));
}
handlersInMethod.put(e.getKey(), handlers);
}
}
return handlersInMethod;
}
所形成的map结果如下图:
这里写图片描述上面一直再讲检索扫描,然后到形成集合。那么是什么时候开始开始扫描的呢?就是target对象注册到Bus对象的时候,也就是new Bus().register(target)执行时进行扫描。事实上上面讲的HandlerFinder接口的调用就是在register方法中执行的。具体的可以自行阅读代码。
对比第一个表格和第三个表格可以发现,一个类中一个类型的事件可以被多个标注@Subscribe方法来处理,也就是说产生事件和消费事件是一对多关系,一个@Produce产生的事件可以被多个@Subsribe来处理,当然@Subsribe消费的事件的类型,必须和@Produce产生的事件类型一样。具体见下文分析。
那么两种注解方法是怎么通信的呢?也就是说@Produce 方法执行完毕时怎么通知@Subsribe方法的呢,在register方法中有这么一段:
//遍历Procuder集合的key
for (Class<?> type : foundProducers.keySet()) {
//查找key对应的EventProducer对象
final EventProducer producer = foundProducers.get(type);
//省略部分代码
//根据key查找能消费key事件的@Subscribe方法集合
Set<EventHandler> handlers = handlersByType.get(type);
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);
}
}
上面的代码很简单,拿事件A来说,先根据A.class找到EventProducer对象,该对象会产生事件A;然后根据A.class找到能消费掉A事件的EventHandler事件集合,交给dispatchProducerResultToHandler方法:
private void dispatchProducerResultToHandler(EventHandler handler, EventProducer producer) {
//生成事件
Object event = producer.produceEvent();
//消费事件
dispatch(event, handler);
}
protected void dispatch(Object event, EventHandler wrapper) {
wrapper.handleEvent(event);
}
//EventProducer类中生产事件的方法:
public Object produceEvent() throws InvocationTargetException {
//反射方式执行@Produce方法
return method.invoke(target);
}
//EventHandler类中消费事件的方法
public void handleEvent(Object event) throws InvocationTargetException {
//反射凡是执行@Subscribe方法
method.invoke(target, event);
}
在此处有一个疑问,事件的生产是在循环中进行的:
for (EventHandler handler : handlers) {
dispatchProducerResultToHandler(handler, producer);
}
也就是说一个@Produce的方法如果对应的@Subscribe方法有多个的话,producer.produceEvent会调用多次,这样的设计意图暂时没想明白为什么,还是说这本身就是一个异常代码,如有知情者,欢迎不吝赐教。为什么不这样调用:
//确保只生产一次事件
Object event = producer.produceEvent
for (EventHandler handler : handlers) {
handler.handleEvent(event)
}
到此为止,Otto的基本逻辑已经分析完毕,只有Bus的post方式发送事件的细节就不多讲了。如有不当之处,欢迎批评指正。
网友评论