Android编译时代码生成之三(实现自己的事件总线)

博客搬迁到这里 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类的逻辑。看下项目结构

Android编译时代码生成之三(实现自己的事件总线)_第1张图片
image

定义注解

定义枚举类型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 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中会调用
   */
  public Disposable register(Class event, Consumer observer, Scheduler scheduler) {
    Flowable flowable = flowableProcessor.ofType(event).observeOn(scheduler);
    Disposable disposable = flowable.subscribe(observer);
    return disposable;
  }
}
 
 

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 set, RoundEnvironment roundEnvironment) {
    Set 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 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 能力有限,如果有什么错误或者有待优化的地方,欢迎指正和交流。

你可能感兴趣的:(Android编译时代码生成之三(实现自己的事件总线))