EventBus(一) 简单使用,不使用反射,使用注解处理器
EventBus(二)使用反射的方式的原理
EventBus(三)手写EventBus3.1.1
发布粘性事件:触发了所有同类型订阅方法(粘性和非粘性)
粘性事件订阅方法无法第2次消费(很难满足复杂项目要求)
多次调用和移除粘性事件时,post会执行多次粘性事件订阅方法(非粘性正常)
优化索引方法,让api更简单直接
重写注解处理器,方式: apt + javapoet (官方是传统写法)
弱化了线程池,使用缓存线程池替代
去除了对象池概念,考虑recycle问题。暂未测试内存泄漏情况
修复Subscription对象匹配bug,删除hashCode无用方法。
通过apt+javapoet技术, 在编译时期,将注册了@Subscribe注解的方法,以class为key,多个方法的封装List为Value放到全局变量map中,存放在apt生成的java类中。在post的时候,使用apt生成的java类对象找到注册的方法,从而使用反射技术调用。 如果指定了线程模式,则需要有线程调度的实现。如果有指定顺序,则重新排列List的顺序 。
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
/**
* 线程模式
*/
public enum ThreadMode {
// 订阅、发布在同一线程。避免了线程切换,也是推荐的默认模式
POSTING,
// 主线程(UI线程)中被调用,切勿耗时操作
MAIN,
// 用于网络访问等耗时操作,事件总线已完成的异步订阅通知线程。并使用线程池有效地重用
ASYNC
}
/**
* 事件订阅方法封装类
*/
public class SubscriberMethod {
private String methodName; // 订阅方法名
private Method method; // 订阅方法,用于最后的自动执行订阅方法
private ThreadMode threadMode; // 线程模式
private Class> eventType; // 事件对象Class,如:UserInfo.class
private int priority; // 事件订阅优先级(实现思路:重排序集合中方法的顺序)
private boolean sticky; // 是否粘性事件(实现思路:发送时存储,注册时判断粘性再激活)
public SubscriberMethod(Class subscriberClass, String methodName,
Class> eventType, ThreadMode threadMode, int priority, boolean sticky) {
this.methodName = methodName;
this.threadMode = threadMode;
this.eventType = eventType;
this.priority = priority;
this.sticky = sticky;
try {
// 订阅所属类(参考源码:AbstractSubscriberInfo.java - 72行)
method = subscriberClass.getDeclaredMethod(methodName, eventType);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
}
public String getMethodName() {
return methodName;
}
public Method getMethod() {
return method;
}
public ThreadMode getThreadMode() {
return threadMode;
}
public Class> getEventType() {
return eventType;
}
public int getPriority() {
return priority;
}
public boolean isSticky() {
return sticky;
}
}
/**
* 所有事件集合
*/
public class EventBeans implements SubscriberInfo {
// 订阅者对象Class,如:MainActivity.class
private final Class subscriberClass;
// 订阅方法数组,参考SimpleSubscriberInfo.java 25行
private final SubscriberMethod[] methodInfos;
public EventBeans(Class subscriberClass, SubscriberMethod[] methodInfos) {
this.subscriberClass = subscriberClass;
this.methodInfos = methodInfos;
}
@Override
public Class> getSubscriberClass() {
return subscriberClass;
}
@Override
public synchronized SubscriberMethod[] getSubscriberMethods() {
return methodInfos;
}
}
public interface SubscriberInfo {
// 订阅所属类,比如:MainActivity
Class> getSubscriberClass();
// 获取订阅所属类中所有订阅事件的方法(此处不使用List是因为注解处理器每次都要list.clear(),麻烦!)
SubscriberMethod[] getSubscriberMethods();
}
/**
* 所有的事件订阅方法,生成索引接口
*/
public interface SubscriberInfoIndex {
/**
* 生成索引接口,通过订阅者对象(MainActivity.class)获取所有订阅方法
*
* @param subscriberClass 订阅者对象Class,如:MainActivity.class
* @return 事件订阅方法封装类
*/
SubscriberInfo getSubscriberInfo(Class> subscriberClass);
}
@Target(ElementType.METHOD) // 该注解作用在方法之上
@Retention(RetentionPolicy.CLASS) // 要在编译时进行一些预处理操作,注解会在class文件中存在
public @interface Subscribe {
// 线程模式,默认推荐POSTING(订阅、发布在同一线程)
ThreadMode threadMode() default ThreadMode.POSTING;
// 是否使用粘性事件
boolean sticky() default false;
// 事件订阅优先级,在同一个线程中。数值越大优先级越高。
int priority() default 0;
}
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
// As-3.4.1 + gradle5.1.1-all + auto-service:1.0-rc4
compileOnly 'com.google.auto.service:auto-service:1.0-rc4'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4'
// 帮助我们通过类调用的形式来生成Java代码
implementation "com.squareup:javapoet:1.9.0"
// 引入annotation,处理@Subscribe注解
implementation project(':eventbus_annotation')
}
// java控制台输出中文乱码
tasks.withType(JavaCompile) {
options.encoding = "UTF-8"
}
sourceCompatibility = "7"
targetCompatibility = "7"
/**
* 编码此类1句话:细心再细心,出了问题debug真的不好调试
*/
// 用来生成 META-INF/services/javax.annotation.processing.Processor 文件
@AutoService(Processor.class)
// 允许/支持的注解类型,让注解处理器处理
@SupportedAnnotationTypes({Constants.SUBSCRIBE_ANNOTATION_TYPES})
// 指定JDK编译版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
// 注解处理器接收的参数
@SupportedOptions({Constants.PACKAGE_NAME, Constants.CLASS_NAME})
public class SubscribeProcessor extends AbstractProcessor {
// 操作Element工具类 (类、函数、属性都是Element)
private Elements elementUtils;
// type(类信息)工具类,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用来报告错误,警告和其他提示信息
private Messager messager;
// 文件生成器 类/资源,Filter用来创建新的类文件,class文件以及辅助文件
private Filer filer;
// APT包名
private String packageName;
// APT类名
private String className;
// 临时map存储,用来存放订阅方法信息,生成路由组类文件时遍历
// key:组名"MainActivity", value:MainActivity中订阅方法集合
private final Map> methodsByClass = new HashMap<>();
// 该方法主要用于一些初始化的操作,通过该方法的参数ProcessingEnvironment可以获取一些列有用的工具类
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
// 初始化
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
// 通过ProcessingEnvironment去获取对应的参数
Map options = processingEnvironment.getOptions();
if (!EmptyUtils.isEmpty(options)) {
packageName = options.get(Constants.PACKAGE_NAME);
className = options.get(Constants.CLASS_NAME);
messager.printMessage(Diagnostic.Kind.NOTE,
"packageName >>> " + packageName + " / className >>> " + className);
}
// 必传参数判空(乱码问题:添加java控制台输出中文乱码)
if (EmptyUtils.isEmpty(packageName) || EmptyUtils.isEmpty(className)) {
messager.printMessage(Diagnostic.Kind.ERROR, "注解处理器需要的参数为空,请在对应build.gradle配置参数");
}
}
/**
* 相当于main函数,开始处理注解
* 注解处理器的核心方法,处理具体的注解,生成Java文件
*
* @param set 使用了支持处理注解的节点集合
* @param roundEnvironment 当前或是之前的运行环境,可以通过该对象查找的注解。
* @return true 表示后续处理器不会再处理(已经处理完成)
*/
@Override
public boolean process(Set extends TypeElement> set, RoundEnvironment roundEnvironment) {
// 一旦有类之上使用@Subscribe注解
if (!EmptyUtils.isEmpty(set)) {
// 获取所有被 @Subscribe 注解的 元素集合
Set extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Subscribe.class);
if (!EmptyUtils.isEmpty(elements)) {
// 解析元素
try {
parseElements(elements);
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
return true;
}
return false;
}
// 解析所有被 @Subscribe 注解的 类元素集合
private void parseElements(Set extends Element> elements) throws IOException {
// 遍历节点
for (Element element : elements) {
// @Subscribe注解只能在方法之上(尽量避免使用instanceof进行判断)
if (element.getKind() != ElementKind.METHOD) {
messager.printMessage(Diagnostic.Kind.ERROR, "仅解析@Subscribe注解在方法上元素");
return;
}
// 强转方法元素
ExecutableElement method = (ExecutableElement) element;
// 检查方法,条件:订阅方法必须是非静态的,公开的,参数只能有一个
if (checkHasNoErrors(method)) {
// 获取封装订阅方法的类(方法上一个节点)
TypeElement classElement = (TypeElement) method.getEnclosingElement();
// 以类名为key,保存订阅方法
List methods = methodsByClass.get(classElement);
if (methods == null) {
methods = new ArrayList<>();
methodsByClass.put(classElement, methods);
}
methods.add(method);
}
messager.printMessage(Diagnostic.Kind.NOTE, "遍历注解方法:" + method.getSimpleName().toString());
}
// 通过Element工具类,获取SubscriberInfoIndex类型
TypeElement subscriberIndexType = elementUtils.getTypeElement(Constants.SUBSCRIBERINFO_INDEX);
// 生成类文件
createFile(subscriberIndexType);
}
private void createFile(TypeElement subscriberIndexType) throws IOException {
// 添加静态块代码:SUBSCRIBER_INDEX = new HashMap();
CodeBlock.Builder codeBlock = CodeBlock.builder();
codeBlock.addStatement("$N = new $T<$T, $T>()",
Constants.FIELD_NAME,
HashMap.class,
Class.class,
SubscriberInfo.class);
// 双层循环,第一层遍历被@Subscribe注解的方法所属类。第二层遍历每个类中所有订阅的方法
for (Map.Entry> entry : methodsByClass.entrySet()) {
// 此处不能使用codeBlock,会造成错误嵌套
CodeBlock.Builder contentBlock = CodeBlock.builder();
CodeBlock contentCode = null;
String format;
for (int i = 0; i < entry.getValue().size(); i++) {
// 获取每个方法上的@Subscribe注解中的注解值
Subscribe subscribe = entry.getValue().get(i).getAnnotation(Subscribe.class);
// 获取订阅事件方法所有参数
List extends VariableElement> parameters = entry.getValue().get(i).getParameters();
// 获取订阅事件方法名
String methodName = entry.getValue().get(i).getSimpleName().toString();
// 注意:此处还可以做检查工作,比如:参数类型必须是类或接口类型(这里缩减了)
TypeElement parameterElement = (TypeElement) typeUtils.asElement(parameters.get(0).asType());
// 如果是最后一个添加,则无需逗号结尾
if (i == entry.getValue().size() - 1) {
format = "new $T($T.class, $S, $T.class, $T.$L, $L, $L)";
} else {
format = "new $T($T.class, $S, $T.class, $T.$L, $L, $L),\n";
}
// new SubscriberMethod(MainActivity.class, "abc", UserInfo.class, ThreadMode.POSTING, 0, false)
contentCode = contentBlock.add(format,
SubscriberMethod.class,
ClassName.get(entry.getKey()),
methodName,
ClassName.get(parameterElement),
ThreadMode.class,
subscribe.threadMode(),
subscribe.priority(),
subscribe.sticky())
.build();
}
if (contentCode != null) {
// putIndex(new EventBeans(MainActivity.class, new SubscriberMethod[] {)
codeBlock.beginControlFlow("putIndex(new $T($T.class, new $T[]",
EventBeans.class,
ClassName.get(entry.getKey()),
SubscriberMethod.class)
// 嵌套的精华(尝试了很多次,有更好的方式请告诉我)
.add(contentCode)
// ))}
.endControlFlow("))");
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "注解处理器双层循环发生错误!");
}
}
// 全局属性:Map, SubscriberMethod>
TypeName fieldType = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(Class.class), // Map
);
// putIndex方法参数:putIndex(SubscriberInfo info)
ParameterSpec putIndexParameter = ParameterSpec.builder(
ClassName.get(SubscriberInfo.class),
Constants.PUTINDEX_PARAMETER_NAME)
.build();
// putIndex方法配置:private static void putIndex(SubscriberMethod info) {
MethodSpec.Builder putIndexBuidler = MethodSpec
.methodBuilder(Constants.PUTINDEX_METHOD_NAME) // 方法名
.addModifiers(Modifier.PRIVATE, Modifier.STATIC) // private static修饰符
.addParameter(putIndexParameter); // 添加方法参数
// 不填returns默认void返回值
// putIndex方法内容:SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
putIndexBuidler.addStatement("$N.put($N.getSubscriberClass(), $N)",
Constants.FIELD_NAME,
Constants.PUTINDEX_PARAMETER_NAME,
Constants.PUTINDEX_PARAMETER_NAME);
// getSubscriberInfo方法参数:Class subscriberClass
ParameterSpec getSubscriberInfoParameter = ParameterSpec.builder(
ClassName.get(Class.class),
Constants.GETSUBSCRIBERINFO_PARAMETER_NAME)
.build();
// getSubscriberInfo方法配置:public SubscriberMethod getSubscriberInfo(Class> subscriberClass) {
MethodSpec.Builder getSubscriberInfoBuidler = MethodSpec
.methodBuilder(Constants.GETSUBSCRIBERINFO_METHOD_NAME) // 方法名
.addAnnotation(Override.class) // 重写方法注解
.addModifiers(Modifier.PUBLIC) // public修饰符
.addParameter(getSubscriberInfoParameter) // 方法参数
.returns(SubscriberInfo.class); // 方法返回值
// getSubscriberInfo方法内容:return SUBSCRIBER_INDEX.get(subscriberClass);
getSubscriberInfoBuidler.addStatement("return $N.get($N)",
Constants.FIELD_NAME,
Constants.GETSUBSCRIBERINFO_PARAMETER_NAME);
// 构建类
TypeSpec typeSpec = TypeSpec.classBuilder(className)
// 实现SubscriberInfoIndex接口
.addSuperinterface(ClassName.get(subscriberIndexType))
// 该类的修饰符
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
// 添加静态块(很少用的api)
.addStaticBlock(codeBlock.build())
// 全局属性:private static final Map, SubscriberMethod> SUBSCRIBER_INDEX
.addField(fieldType, Constants.FIELD_NAME, Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
// 第一个方法:加入全局Map集合
.addMethod(putIndexBuidler.build())
// 第二个方法:通过订阅者对象(MainActivity.class)获取所有订阅方法
.addMethod(getSubscriberInfoBuidler.build())
.build();
// 生成类文件:EventBusIndex
JavaFile.builder(packageName, // 包名
typeSpec) // 类构建完成
.build() // JavaFile构建完成
.writeTo(filer); // 文件生成器开始生成类文件
}
/**
* 检查方法,条件:订阅方法必须是非静态的,公开的,参数只能有一个
*
* @param element 方法元素
* @return 检查是否通过
*/
private boolean checkHasNoErrors(ExecutableElement element) {
// 不能为static静态方法
if (element.getModifiers().contains(Modifier.STATIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, "订阅事件方法不能是static静态方法", element);
return false;
}
// 必须是public修饰的方法
if (!element.getModifiers().contains(Modifier.PUBLIC)) {
messager.printMessage(Diagnostic.Kind.ERROR, "订阅事件方法必须是public修饰的方法", element);
return false;
}
// 订阅事件方法必须只有一个参数
List extends VariableElement> parameters = ((ExecutableElement) element).getParameters();
if (parameters.size() != 1) {
messager.printMessage(Diagnostic.Kind.ERROR, "订阅事件方法有且仅有一个参数", element);
return false;
}
return true;
}
}
implementation project(':eventbus_annotation')
增加注解的依赖。
/**
* 临时JavaBean对象,也可以直接写在EventBus做为变量
*/
final class Subscription {
final Object subscriber; // 订阅者MainActivity.class
final SubscriberMethod subscriberMethod; // 订阅的方法
Subscription(Object subscriber, SubscriberMethod subscriberMethod) {
this.subscriber = subscriber;
this.subscriberMethod = subscriberMethod;
}
@Override
public boolean equals(Object other) {
// 必须重写方法,检测激活粘性事件重复调用(同一对象注册多个)
if (other instanceof Subscription) {
Subscription otherSubscription = (Subscription) other;
// 删除官方:subscriber == otherSubscription.subscriber判断条件
// 原因:粘性事件Bug,多次调用和移除时重现,参考Subscription.java 37行
return subscriberMethod.equals(otherSubscription.subscriberMethod);
} else {
return false;
}
}
}
/**
* ArrayList的底层是数组,查询和修改直接根据索引可以很快找到对应的元素(替换)
* 而增加和删除就涉及到数组元素的移动,所以会比较慢
*
* CopyOnWriteArrayList实现了List接口(读写分离)
* Vector是增删改查方法都加了synchronized,保证同步,但是每个方法执行的时候都要去获得锁,性能就会大大下降
* 而CopyOnWriteArrayList 只是在增删改上加锁,但是读不加锁,在读方面的性能就好于Vector
*
* CopyOnWriteArrayList支持读多写少的并发情况
*/
public class EventBus {
// volatile修饰的变量不允许线程内部缓存和重排序,即直接修改内存
private static volatile EventBus defaultInstance;
// 索引接口
private SubscriberInfoIndex subscriberInfoIndexes;
// 订阅者类型集合,比如:订阅者MainActivity订阅了哪些EventBean,或者解除订阅的缓存。
// key:订阅者MainActivity.class,value:EventBean集合
private Map