上篇博文《Otto源码解读》简单分析了Otto的实现原理,总的来说就是检索收集各个注册对象中的@Subscribe方法,然后用反射method.invoke(targetObj,event)执行之,但是对这些注解方法的检索收集是在运行时期进行的,所以效率上难免会有些不尽人意。本篇博文就运用AbstractProcessor将注解方法的检索放在编译期间就搞定,实现自己的Otto。
当然本文的Otto没有实用性价值,因为在EventBus这中强大的面前,自己的只是简单的小儿科,通过这篇博文也是对自己有关知识点的巩固,项目源码在此。
本文的核心就是将Otto原来的运行时检索@Subscribe方法的过程,通过AbstractProcessor转移到编译时进行。
也就是说在编译期间将应用中标有@Subscribe的方法收集成起来,放入某个static变量的集合中去:
private static final Map, SubscriberInfo> SUBSCRIBERES;
为了博文的连贯性,本篇不会再赘述AbstractProcessor的使用方法,不明白的可以先阅读Android编译时注解APT实战体会下;或者下载博主的源码来看。
闲言少叙,立马开车。
AbstractProcessor的在本文的主要目的就是:
下面进行详细说明。
对于AbstractProcessor来说,是在其process方法里面进行的:
/**
*SupportedAnnotationTypes标明要扫描的注解*类
*AutoService注解帮助自动生成resources/META-INF/services/javax.annotation.processing.Processor文件
*
*/
@SupportedAnnotationTypes("com.yanqiu.otto.bus.subscribe.Subscribe")
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@AutoService(Processor.class)
public class SubscribeProcessor extends AbstractProcessor {
private Messager mMessager;
private Filer mFiler;
private Types mTypes;
private final Map> methodsByClass = new HashMap<>();
@Override
public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEv) {
//1、收集各个类中的@Subscribe方法
collectSubscribesMethod(annotations, roundEv);
//2、将收集到的信息动态创建
createJavaFile();
return false;
}
}
下面就来看看collectSubscribesMethod方法都做了什么:
private final Map> methodsByClass = new HashMap<>();
private void collectSubscribesMethod(Set extends TypeElement> annotations, RoundEnvironment roundEv) {
//遍历@Subscribe标注的元素
for (Element annotatedElement : roundEv.getElementsAnnotatedWith(Subscribe.class)) {
//如果是方法
if (annotatedElement instanceof ExecutableElement) {
ExecutableElement method = (ExecutableElement) annotatedElement;
if (checkHasNoErrors(method)) {
//通过getEnclosingElement获取方法所在的类,比如MainActivity中sub1和sub2方法的classElement就是com.apt.otto.MainActivity
TypeElement classElement = (TypeElement) method.getEnclosingElement();
Set methods = methodsByClass.get(classElement);
if (methods == null) {
methods = new HashSet();
methodsByClass.put(classElement, methods);
}
methods.add(method);
}
}
}//end for
}
}
collectSubscribesMethod主要是遍历类中标注有@Subscribe的方法,在这里有有个Element的概念,在这里用下面的列表简单解释一下各个Element和java代码中的对应关系:
代码 | Element |
---|---|
package com.apt.otto.Demo | PackageElement |
public class Demo{ | TypeElement |
private String value; | VariableElement |
public Demo() | ExecuteableElement |
public void setValue(String value) | ExecuteableElement |
而我们的@Subscribe注解是作用在方法上的,所以我们在collectSubscribesMethod需要关注ExecuteableElement:
if (annotatedElement instanceof ExecutableElement){
。。。。
}
同样的,因为还需要知道标注了@Subscribe的某个方法位于哪个类中,Element的getEnclosingElement()可以做到这一点,比如:
package com.apt.otto;
public class MainActivity extends Activity {
@Subscribe
public void sub1(Demo demo) {
}
}
扫描获取大sub1这个ExecuteableElement对象,然后该对象调用getEnclosingElement(),打印其toString()将获取到com.apt.otto.MainActivity这一串信息。而这一信息就做为methodsByClass这个map的key来存储了。
当然一个类中的多个方法可以添加@Subscribe注解,是一对多的关系,所以methodsByClass的value就是一个Set集合来存储一个类中的多个注解方法。
经过collectSubscribesMethod处理之后,我们得到了methodsByClass这个map,现在就需要将该map所保存的数据写到一个类里面,所以看看createJavaFile方法都做了什么:
private void createJavaFile() {
//省略部分代码
BufferedWriter writer = null;
try {
String packageName = "com.yanqiu.otto.auto";
JavaFileObject sourceFile = mFiler.createSourceFile(packageName + ".SubscribeUtils");
writer = new BufferedWriter(sourceFile.openWriter());
writer.write("package " + packageName + ";\n");
writer.write("import com.yanqiu.otto.bus.subscribe.SubscriberMethodInfo;\n");
writer.write("import com.yanqiu.otto.bus.subscribe.SubscriberInfo;\n");
writer.write("import com.yanqiu.otto.bus.SubscriberInfoFinder;\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by OttBus, do not edit. */\n");
writer.write("public class SubscribeUtils implements SubscriberInfoFinder{\n");
writer.write(" private static final Map, SubscriberInfo> SUBSCRIBERES;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBERES = new HashMap, SubscriberInfo>();\n\n");
writeIndexLines(writer);
writer.write(" }\n\n");
writer.write(" private static void addSubscribeInfo(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBERES.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBERES.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
} finally {
//省略部分代码
}
}
最终我们动态创建的类如下:
public class SubscribeUtils implements SubscriberInfoFinder{
//注解方法的集合,key是目标类class对象,value是该类对应的注解方法集合
private static final Map, SubscriberInfo> SUBSCRIBERES;
static {
SUBSCRIBERES = new HashMap, SubscriberInfo>();
addSubscribeInfo(new SubscriberInfo(com.apt.otto.MainActivity.class ,new SubscriberMethodInfo[] {
new SubscriberMethodInfo("sub2", String.class),
new SubscriberMethodInfo("sub1", com.apt.otto.Demo.class),
new SubscriberMethodInfo("sub3", String.class)
}));
}
private static void addSubscribeInfo(SubscriberInfo info) {
SUBSCRIBERES.put(info.getSubscriberClass(), info);
}
@Override
public SubscriberInfo getSubscriberInfo(Class> subscriberClass) {
SubscriberInfo info = SUBSCRIBERES.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
通过生成的代码SubscribeUtils我们发现主要是将:
private final Map> methodsByClass = new HashMap<>();
映射成了:
private static final Map, SubscriberInfo> SUBSCRIBERES;
其中SUBSCRIBERES这个map的key指的是@Subscribe方法所在的类信息,由methodsByClass的key映射而成;而methodsByClass的value则映射成SubscriberInfo一个对象。该对象包含了如下信息:
public class SubscriberInfo {
//@Subscribe的宿主,比如MainActivity.class
private final Class subscriberClass;
//某类中标有@Subscribe方法信息集合
private final SubscriberMethodInfo[] methodInfos;
}
用图片表示SubScriberInfo的话,如下图:
其中SubscriberInfo的methodInfos数组则是由methodsByClass的value这个set集合映射而来。
比如通过扫描MainActvity这个类之后:
public class MainActivity extends Activity {
@Subscribe
public void sub1(Demo demo) {
}
@Subscribe
public void sub2(String view) {
}
@Subscribe
public void sub3(String view) {
}
}
我们得到一个如下的SubscriberInfo对象:
SubscriberMethodInfo[] methods=new SubscriberMethodInfo[] {
new SubscriberMethodInfo("sub2", String.class),
new SubscriberMethodInfo("sub1", com.apt.otto.Demo.class),
new SubscriberMethodInfo("sub3", String.class)
}
//扫描得到的SubscriberInfo对象
new SubscriberInfo(com.apt.otto.MainActivity.class ,methods)
用图片直观表示如下图:
SubscriberMethodInfo封装的信息如下:
public class SubscriberMethodInfo {
//方法名
final String methodName;
//方法参数的类型
final Class> paramClass;
}
到此为止由AbstractProcessor扫描注解到创建java文件的过程分析完毕,需要注意的是动态创建的类SubscribeUtils实现了SubscriberInfoFinder接口:
class SubscribeUtils implements SubscriberInfoFinder{
}
//SubscriberInfoFinder接口信息
public interface SubscriberInfoFinder {
SubscriberInfo getSubscriberInfo(Class> subscriberClass);
}
之所以定义这个接口,除了方便扩展之外,还有个原因就是SubscribeUtils编译之后的为止在app/build/generated/source/apt/release/之下,我们没办法在别的module中使用,除非在build.gralde中配置依赖信息。因为有了接口,所以我们使用起来很方便,见如下代码:
public class Bus {
private SubscriberInfoFinder subscriberInfoFinder;
public Bus(SubscriberInfoFinder subscriberInfoFinder) {
this.subscriberInfoFinder = subscriberInfoFinder;
}
这样我们在Mactivity中就可以这样调用:
//将动态生成的SubscribeUtils传入的Bus中
private static Bus bus = new Bus(new SubscribeUtils());
通过上面的解释,我们获取注解文件的映射信息,但是我们怎么使用呢?
通过Otto源码解读,我们知道最终需要通过:
method.invoke(targetObj,event)
来完成整个调用。那么现在我们还缺少两种对象,一个是Method对象,一个就是targetObj这个对象。
那么Method对象是怎么产生的呢?我们知道获取一个类的Method对象可以通过下面的方式:
Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
需要一个类的Class对象,方法名称,方法参数类型,我们就可以得到一个Methodd对象。而上文中的SubscriberMethodInfo对象正好包含了这些信息。且SubscriberInfo中包含了SubscriberMethodInfo[] methodInfos数组,所以在SubscriberInfo提供了getSubscriberMethods方法:
public synchronized SubscriberMethod[] getSubscriberMethods() {
int length = methodInfos.length;
//SubscriberMethod封装了方法名和方法参数类型
SubscriberMethod[] methods = new SubscriberMethod[length];
for (int i = 0; i < length; i++) {
SubscriberMethodInfo info = methodInfos[i];
methods[i] = createSubscriberMethod(info.methodName, info.paramClass);
}
return methods;
}
public SubscriberMethod createSubscriberMethod(String methodName, Class> paramType) {
//根据方法名和参数获取subscriberClass的对应方法
Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
return new SubscriberMethod(method, paramType);
}
该方法返回了个SubscriberMethod数组,SubscriberMethod对象封装了如下信息:
SubscriberMethod {
public final Method method;
//事件类型
public final Class> eventType;
}
峰回路转,终于看到了熟悉的Method对象,可以说整个流程下来没什么难度,就是为了下面这一行代码服务:
Method method = subscriberClass.getDeclaredMethod(methodName, paramType);
并最终生成了SubscriberMethod,图片表示如下:
因为运行method需要具体的Object对象,所以Bus类提供了register方法:
public void register(Object subscriber) {
Class> subscriberClass = subscriber.getClass();
//获取Mactivity对应的SubscriberInfo对象
SubscriberInfo subscriberInfo = subscriberInfoFinder.getSubscriberInfo(subscriberClass);
//获取Mactivity中对应的注解方法
SubscriberMethod[] subscriberMethods = subscriberInfo.getSubscriberMethods();
synchronized (this) {
//遍历所有的注解方法
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
private final Map, CopyOnWriteArrayList> eventHandlerMap;
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//搜索subscriber对象的事件类型
Class> eventType = subscriberMethod.eventType;
//封装事件处理类
EventHandler eventHandler = new EventHandler(subscriber, subscriberMethod);
CopyOnWriteArrayList eventHandlers = eventHandlerMap.get(eventType);
if (eventHandlers == null) {
eventHandlers = new CopyOnWriteArrayList<>();
eventHandlerMap.put(eventType, eventHandlers);
}
eventHandlers.add(eventHandler);
}
注解的时候先获取subscriber的SubscriberInfo对象(见上图),然后获取subscriber的标有注解的方法组成的SubscriberMethod数据对象。
然后将每个SubscriberMethod转换成EventHandler(新版本的EventBus名字改成了Subscription) ,所以EventHandler 就如下图所示:
通过注册方法将subscriber对象与subscriberMethod绑定形成了EventHandler对象,需要注意的是事件可能被多个注解方法处理,所以eventType和EventHandler是一对多的关系,所以Bus类中提供了:
//key为事件类型,value为EventHandler
private final Map, CopyOnWriteArrayList> eventHandlerMap;
EventHandler封装的数据如下:
public class EventHandler {
final Object subscriber;
final SubscriberMethod subscriberMethod;
}
以上面的demo代码MainActivity来看,最终eventHandlerMap形成构成的数据如下:
那么我们在发送事件的时候,就能正确的运行注解方法了,看看Bus类post方法做了什么:
public void post(Object event) {
//获取事件类型
Class> targetType = event.getClass();
//获取匹配事件的处理类集合
List eventHandlers = eventHandlerMap.get(targetType);
if (eventHandlers != null) {
for (EventHandler eventHandler : eventHandlers) {
if (eventHandler.isMatch(targetType)) { //方法处理
eventHandler.handleEvent(event);
}
}
}
}
找到能处理targetType事件的EventHandle列表,循环之,调用handleEvent处理即可:
public void handleEvent(Object event) {
//简单的反射调用
subscriberMethod.method.invoke(subscriber, event);
}
到此为止,分析完毕,如果觉得读起来很拗口(因为本文写起来都觉得很啰嗦)可以参考博客源码
来进行分析理解,还是有一定的学习参考价值的。因为本文的代码参考了EventBus的源码,可以说是EventBus的迷你版,窥一斑而知全豹,从中也可以体会到EventBus的实现原理,若果想分析EventBusy源码的话,本篇博文也算是起到穿针引线的作用。
总之核心就是扫描注解信息,生成java文件,然后将注解的方法获取对应的Method对象进行invoke调用,仅此而已。只不过处理的过程中涉及的对象比较多,所以本文写起来比较啰哩啰嗦,如有不当之处,欢迎批评指正
最后使用demo如下:
public class Demo {
private static Bus bus = new Bus(new SubscribeUtils());
public Demo() {
bus.register(this);
}
@Subscribe
public void subScribeDemo(String msg) {
System.out.println("收到了消息: " + msg);
}
public void destroy() {
bus.unregister(this);
}
public static void main(String args[]) {
Demo demo = new Demo();
bus.post("Hello Apt Otto");
demo.destroy();
bus.post("Hello Apt Otto again");
}
}
Android编译时注解APT实战
EventBus源码
Otto源码
博客源码
Otto源码解读