博客搬遷到這里 http://blog.fdawei.club,歡迎訪問,大家一起學(xué)習(xí)交流。
學(xué)會了關(guān)于注解、Apt以及javapoet的這些知識后,我們就可以做很多有趣的事情了。
還不了解的話,可以去看看前面兩篇文章 Android編譯時代碼生成之一(注解與APT) 和 Android編譯時代碼生成之二(javapoet)
Android開發(fā)中,相信大家都知道大名鼎鼎的EventBus,尤其是它基于注解的發(fā)布與訂閱,更是讓我們大呼神器。不過,自從RxJava被大家廣泛使用了之后,我們已經(jīng)沒有了引入EventBus的必然需要。因為RxJava本身就是基于觀察者模式的,使用它,我們很容易自己實現(xiàn)像EventBus這種的事件總線。
概述
RxJava提供了對事件的處理,我們需要做的是通過注解來簡化訂閱方式。我們將我們的事件總線命名為RxBus。參考了EventBus中訂閱事件的方式,在需要響應(yīng)事件的方法上使用注解@Subscrible,在合適的時候?qū)⒃摲椒ㄋ鶎俚念悓ο笞缘絉xBus中進行事件的訂閱,并在不需要的時候取消訂閱。為了能如此方便的使用,我們需要做哪些事情呢?
事件的發(fā)送與接收,我們使用RxJava。在一個對象被注冊到RxBus中的時候,我們需要獲取該對象中被Subsrible注解標記的方法,并添加訂閱回調(diào)到RxJava中,保證在事件被接收到時觸發(fā)該方法。如何實現(xiàn)在接收到事件時調(diào)用對應(yīng)的方法呢?很容易想到的方式就是使用反射。反射是一種方法,不過反射的缺點大家也知道,我們這里不使用。本篇文章的主題是Android編譯時代碼生成,對,我們使用的就是apt加javapoet。
我們添加針對Subscrible注解的編譯時注解處理器,在處理器中對包含@Subscrible方法的類生成對應(yīng)的代理類,在代理類中添加RxJava的事件訂閱的回調(diào),回調(diào)方法中再調(diào)用被代理類的事件處理方法。
語言描述比較抽象,我們先了解個大概的實現(xiàn)思路,具體的實現(xiàn)看下面的詳細分析
項目中添加兩個Java Module,分別為 rxbus 和 rxbus-processor。前者主要是對RxJava的一些封裝來,后者就是編譯時注解處理器自動生成Proxy類的邏輯??聪马椖拷Y(jié)構(gòu)

定義注解
定義枚舉類型ThreadMode,用來表示事件處理方法執(zhí)行的線程
public enum ThreadMode {
CURRENT, //當前線程
MAIN, //主線程(UI線程)
IO, //對應(yīng) Schedulers.io()
COMPUTATION, //對應(yīng) Schedulers.computation()
NEW //創(chuàng)建一個新的線程執(zhí)行,對應(yīng) Schedulers.newThread()
}
定義注解Subscribe
@Retention(RetentionPolicy.SOURCE)
@Target(ElementType.METHOD)
public @interface Subscribe {
ThreadMode thread() default ThreadMode.CURRENT;
}
使用RxJava處理事件
RxJava現(xiàn)在已經(jīng)升級到第二版,它與第一版很多地方有挺大的區(qū)別,不過整體思想還是一樣的。這里基于RxJava v2.0.6、RxAndroid v2.0.1。RxJava2中有兩種事件處理方式,Observable和Flowable,前者是無被壓的,后者是有被壓的。何為被壓?如果事件的生產(chǎn)者與時間的消費者不在同一個線程中,事件的生產(chǎn)者產(chǎn)生事件的速度大于事件的消費者處理事件的速度時,事件就會形成積壓,經(jīng)過一段時間后,大量的未處理事件就會擠爆你的內(nèi)存導(dǎo)致OOM。被壓就是在此時,事件消費者通知生產(chǎn)者降低事件發(fā)送速率的一種策略。詳細內(nèi)容可以自行查找相關(guān)資料。我們這里選用Flowable。
RxBusImpl的邏輯比較簡單,通過FlowableProcessor添加訂閱和發(fā)送事件。使用了單例模式。主要就是一些RxJava2中的方法的使用。
public class RxBusImpl {
private static volatile RxBusImpl instance;
private FlowableProcessor<Object> flowableProcessor;
public static RxBusImpl getInstance() {
if (instance != null) {
return instance;
} else {
synchronized (RxBusImpl.class) {
if (instance == null) {
instance = new RxBusImpl();
}
}
return instance;
}
}
private RxBusImpl() {
flowableProcessor = PublishProcessor.create().toSerialized();
}
public void post(Object target) {
flowableProcessor.onNext(target);
}
/**
* RxBusProxy的register中會調(diào)用
*/
public Disposable register(Class event, Consumer observer, Scheduler scheduler) {
Flowable flowable = flowableProcessor.ofType(event).observeOn(scheduler);
Disposable disposable = flowable.subscribe(observer);
return disposable;
}
}
RxBus類是使用時主要調(diào)用的類,他提供了一些靜態(tài)方法。
public class RxBus {
public static final String PROXY_CLASS_SUFFIX = "_RxBusProxy";
private static Map<String, RxBusProxy> 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是用來進行事件訂閱和取消訂閱,傳入的參數(shù)是事件處理方法的類對象。register方法中,會查找對應(yīng)的代理類(所有的代理類都會繼承RxBusProxy接口),實例化并調(diào)用他的register方法??梢韵瓤聪律傻拇眍?/p>
public interface RxBusProxy<S> {
void register(S source);
void unregister();
}
public class MainActivity_RxBusProxy implements RxBusProxy<MainActivity> {
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<RxBusEvent.EventShowNumber>() {
@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<RxBusEvent.EventAddNumber>() {
@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方法用來發(fā)送事件,實際上它會調(diào)用RxBusImpl中的post方法。
接下來就是重頭戲,如何生成這樣的代理類。
編譯時自動生成代理類
直入主題,RxBusProcessor類就是我們的注解處理器。復(fù)寫三個方法
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
filer = processingEnvironment.getFiler();
messager = processingEnvironment.getMessager();
elementUtils = processingEnvironment.getElementUtils();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> 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方法調(diào)用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中保存了需要生成的代理類的信息。得到這些代理類的信息后,就可以生成相應(yīng)的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);
}
代理類實現(xiàn)RxBusProxy接口,其中有一個成員變量compositeDisposable,兩個方法register和unregister。代理類的unregister很簡單,只需要執(zhí)行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稍復(fù)雜,其實可以先自己寫出需要生成的類,再對照此編寫生成代碼的邏輯。
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已經(jīng)放到了Github上 https://github.com/fangdawei/RxJavaDemo 能力有限,如果有什么錯誤或者有待優(yōu)化的地方,歡迎指正和交流。