Android編譯時代碼生成之三(實現(xiàn)自己的事件總線)

博客搬遷到這里 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)

image

定義注解

定義枚舉類型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)化的地方,歡迎指正和交流。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容