博客搬迁到这里 http://blog.fdawei.club,欢迎访问,大家一起学习交流。
学会了关于注解、Apt以及javapoet的这些知识后,我们就可以做很多有趣的事情了。
还不了解的话,可以去看看前面两篇文章 Android编译时代码生成之一(注解与APT) 和 Android编译时代码生成之二(javapoet)
Android开发中,相信大家都知道大名鼎鼎的EventBus,尤其是它基于注解的发布与订阅,更是让我们大呼神器。不过,自从RxJava被大家广泛使用了之后,我们已经没有了引入EventBus的必然需要。因为RxJava本身就是基于观察者模式的,使用它,我们很容易自己实现像EventBus这种的事件总线。
概述
RxJava提供了对事件的处理,我们需要做的是通过注解来简化订阅方式。我们将我们的事件总线命名为RxBus。参考了EventBus中订阅事件的方式,在需要响应事件的方法上使用注解@Subscrible,在合适的时候将该方法所属的类对象注册到RxBus中进行事件的订阅,并在不需要的时候取消订阅。为了能如此方便的使用,我们需要做哪些事情呢?
事件的发送与接收,我们使用RxJava。在一个对象被注册到RxBus中的时候,我们需要获取该对象中被Subsrible注解标记的方法,并添加订阅回调到RxJava中,保证在事件被接收到时触发该方法。如何实现在接收到事件时调用对应的方法呢?很容易想到的方式就是使用反射。反射是一种方法,不过反射的缺点大家也知道,我们这里不使用。本篇文章的主题是Android编译时代码生成,对,我们使用的就是apt加javapoet。
我们添加针对Subscrible注解的编译时注解处理器,在处理器中对包含@Subscrible方法的类生成对应的代理类,在代理类中添加RxJava的事件订阅的回调,回调方法中再调用被代理类的事件处理方法。
语言描述比较抽象,我们先了解个大概的实现思路,具体的实现看下面的详细分析
项目中添加两个Java Module,分别为 rxbus 和 rxbus-processor。前者主要是对RxJava的一些封装来,后者就是编译时注解处理器自动生成Proxy类的逻辑。看下项目结构
定义注解
定义枚举类型ThreadMode,用来表示事件处理方法执行的线程
public enum ThreadMode {
CURRENT, //当前线程
MAIN, //主线程(UI线程)
IO, //对应 Schedulers.io()
COMPUTATION, //对应 Schedulers.computation()
NEW //创建一个新的线程执行,对应 Schedulers.newThread()
}
定义注解Subscribe
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Subscribe {
ThreadMode thread() default ThreadMode.CURRENT;
}
使用RxJava处理事件
RxJava现在已经升级到第二版,它与第一版很多地方有挺大的区别,不过整体思想还是一样的。这里基于RxJava v2.0.6、RxAndroid v2.0.1。RxJava2中有两种事件处理方式,Observable和Flowable,前者是无被压的,后者是有被压的。何为被压?如果事件的生产者与时间的消费者不在同一个线程中,事件的生产者产生事件的速度大于事件的消费者处理事件的速度时,事件就会形成积压,经过一段时间后,大量的未处理事件就会挤爆你的内存导致OOM。被压就是在此时,事件消费者通知生产者降低事件发送速率的一种策略。详细内容可以自行查找相关资料。我们这里选用Flowable。
RxBusImpl的逻辑比较简单,通过FlowableProcessor添加订阅和发送事件。使用了单例模式。主要就是一些RxJava2中的方法的使用。
public class RxBusImpl {
private static volatile RxBusImpl instance;
private FlowableProcessor
RxBus类是使用时主要调用的类,他提供了一些静态方法。
public class RxBus {
public static final String PROXY_CLASS_SUFFIX = "_RxBusProxy";
private static Map proxyMap = new HashMap<>();
public static void register(Object source) {
RxBusProxy proxy = findRxBusProxy(source);
if (proxy != null) {
proxy.register(source);
addRxBusProxy(source, proxy);
}
}
public static void unregister(Object source) {
RxBusProxy proxy = findRxBusProxy(source);
if (proxy != null) {
proxy.unregister();
removeRxBusProxy(source);
}
}
public static void post(Object target) {
RxBusImpl.getInstance().post(target);
}
private static RxBusProxy findRxBusProxy(Object source) {
try {
Class clazz = source.getClass();
String className = clazz.getName();
RxBusProxy proxy = proxyMap.get(className);
if (proxy == null) {
Class proxyClass = Class.forName(className + PROXY_CLASS_SUFFIX);
proxy = (RxBusProxy) proxyClass.newInstance();
}
return proxy;
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
throw new RuntimeException(String.format("can not find %s , something when compiler",
source.getClass().getSimpleName() + PROXY_CLASS_SUFFIX));
}
private static void addRxBusProxy(Object source, RxBusProxy proxy) {
proxyMap.put(source.getClass().getName(), proxy);
}
private static void removeRxBusProxy(Object source) {
Class clazz = source.getClass();
String className = clazz.getName();
RxBusProxy proxy = proxyMap.get(clazz.getName());
if (proxy != null) {
proxyMap.remove(className);
}
}
}
register和unregister是用来进行事件订阅和取消订阅,传入的参数是事件处理方法的类对象。register方法中,会查找对应的代理类(所有的代理类都会继承RxBusProxy接口),实例化并调用他的register方法。可以先看下生成的代理类
public interface RxBusProxy {
void register(S source);
void unregister();
}
public class MainActivity_RxBusProxy implements RxBusProxy {
private CompositeDisposable compositeDisposable = new CompositeDisposable();
public void register(final MainActivity source) {
RxBusImpl rxBusImpl = RxBusImpl.getInstance();
Disposable showToast_disposable = rxBusImpl.register(RxBusEvent.EventShowNumber.class, new Consumer() {
@Override public void accept(RxBusEvent.EventShowNumber o) throws Exception {
source.showToast(o);
}
}, io.reactivex.android.schedulers.AndroidSchedulers.mainThread());
compositeDisposable.add(showToast_disposable);
Disposable addNumber_disposable = rxBusImpl.register(RxBusEvent.EventAddNumber.class, new Consumer() {
@Override public void accept(RxBusEvent.EventAddNumber o) throws Exception {
source.addNumber(o);
}
}, io.reactivex.android.schedulers.AndroidSchedulers.mainThread());
compositeDisposable.add(addNumber_disposable);
}
public void unregister() {
if (compositeDisposable != null && !compositeDisposable.isDisposed()) {
compositeDisposable.dispose();
}
}
}
post方法用来发送事件,实际上它会调用RxBusImpl中的post方法。
接下来就是重头戏,如何生成这样的代理类。
编译时自动生成代理类
直入主题,RxBusProcessor类就是我们的注解处理器。复写三个方法
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set getSupportedAnnotationTypes() {
Set supportedAnnotationTypes = new HashSet<>();
supportedAnnotationTypes.add(Subscribe.class.getCanonicalName());
return supportedAnnotationTypes;
}
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
Set extends Element> subscribeSet = roundEnvironment.getElementsAnnotatedWith(Subscribe.class);
if (subscribeSet != null && subscribeSet.size() > 0) {
processSubscribeAnnotations(subscribeSet, roundEnvironment);
return true;
} else {
return false;
}
}
我们在init中保存我们后面需要的一些对象。
- messager 用来进行日志输出
- filer 用于保存java文件
- elementUtils 对注解元素进行操作的工具
getSupportedAnnotationTypes中过滤出我们需要处理的被Subscribe注解的元素。
process方法使我们的重点。process方法调用processSubscribeAnnotations方法,来看这个方法
private void processSubscribeAnnotations(Set extends Element> set, RoundEnvironment roundEnv) {
for (Element element : set) {
ExecutableElement methodElement = (ExecutableElement) element;
String sourceClassFullName = getClassFullName(methodElement);
ProxyClassInfo proxyClassInfo = proxyClassInfoMap.get(sourceClassFullName);
if (proxyClassInfo == null) {
proxyClassInfo = new ProxyClassInfo(getClassTypeElement(methodElement), getPackageName(methodElement));
proxyClassInfoMap.put(sourceClassFullName, proxyClassInfo);
}
if (checkMethodValid(methodElement)) {
SourceMethodInfo sourceMethodInfo = new SourceMethodInfo(methodElement);
proxyClassInfo.addSourceMethodInfo(sourceMethodInfo);
}
}
try {
for (String sourceClassFullName : proxyClassInfoMap.keySet()) {
ProxyClassInfo proxyClassInfo = proxyClassInfoMap.get(sourceClassFullName);
proxyClassInfo.generateJavaFile(filer);
}
} catch (IOException e) {
error(e.getMessage());
}
}
ProxyClassInfo中保存了需要生成的代理类的信息。得到这些代理类的信息后,就可以生成相应的java文件了。ProxyClassInfo的generateJavaFile就是用来生成java文件的。
public void generateJavaFile(Filer filer) throws IOException {
TypeSpec classType = TypeSpec.classBuilder(proxyClassSimpleName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(ParameterizedTypeName.get(ClassName.get(RxBusProxy.class),
TypeVariableName.get(getTypeSimpleName(sourceClassTypeElement))))
.addField(generateCompositeDisposableField())
.addMethod(generateRegisterMethod())
.addMethod(generateUnregisterMethod())
.build();
JavaFile javaFile = JavaFile.builder(packageName, classType).build();
javaFile.writeTo(filer);
}
代理类实现RxBusProxy接口,其中有一个成员变量compositeDisposable,两个方法register和unregister。代理类的unregister很简单,只需要执行compositeDisposable.dispose()即可。
private MethodSpec generateUnregisterMethod() {
return MethodSpec.methodBuilder("unregister")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.beginControlFlow(String.format("if (%s != null && !%s.isDisposed())", COMPOSITE_DISPOSABLE_FIELD_NAME,
COMPOSITE_DISPOSABLE_FIELD_NAME))
.addStatement(String.format("%s.dispose()", COMPOSITE_DISPOSABLE_FIELD_NAME))
.endControlFlow()
.build();
}
register稍复杂,其实可以先自己写出需要生成的类,再对照此编写生成代码的逻辑。
private MethodSpec generateRegisterMethod() {
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("register")
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(TypeVariableName.get(getTypeSimpleName(sourceClassTypeElement)), "source", Modifier.FINAL);
ClassName rxBusImplClassName = ClassName.get(RxBusImpl.class);
ClassName disposableClassName = ClassName.get(Disposable.class);
ClassName consumerClassName = ClassName.get(Consumer.class);
methodBuilder.addStatement("$T rxBusImpl = $T.getInstance()", rxBusImplClassName, rxBusImplClassName);
for (SourceMethodInfo sourceMthodInfo : sourceMethodInfoList) {
String sourceMethodName = sourceMthodInfo.getMethodName();
String disposableName = sourceMethodName + "_disposable";
ClassName eventClassName = sourceMthodInfo.getEventClassName();
String schedulersCodeString = sourceMthodInfo.getSchedulersCodeString();
methodBuilder.addCode(getRegisterCodeString(disposableName, sourceMethodName, schedulersCodeString),
disposableClassName, eventClassName, consumerClassName, eventClassName, eventClassName);
methodBuilder.addStatement(String.format("%s.add(%s)", COMPOSITE_DISPOSABLE_FIELD_NAME, disposableName));
}
return methodBuilder.build();
}
如果对apt和javapoet熟悉的话,代码还是挺容易理解。
这里只列出了主要的一些代码,详细的Demo已经放到了Github上 https://github.com/fangdawei/RxJavaDemo 能力有限,如果有什么错误或者有待优化的地方,欢迎指正和交流。