Subscriber Index
背景
- Subscriber Index是EventBus 3.0新引入的功能猜极,它能大幅提升初始注冊過程的效率。其原理其實很簡單素跺,就是把運行時期做的事情放到了編譯時期去做,從而提升了運行時期的效率。大家可以從EventBus作者的博客中的對比圖得到感性的認(rèn)識疲牵。 可以看到,開啟了索引的3.0榆鼠,其注冊速度比2.4快了3到6倍纲爸。
- 然而,該新功能是一個可選項妆够,使用者必須通過一定的配置才能使得該新功能得以生效识啦。
前提
- 訂閱者和訂閱事件類必須是Public的,訂閱者方法才能被索引到神妹;另外颓哮,由于Java注解機制自身的限制,在匿名類中的@Subscribe注解是不能被識別的鸵荠,從而不能生成索引冕茅。
- 但這不會造成致命問題,因為在注冊過程中存在這樣的邏輯:如果使用不了索引蛹找,那么程序?qū)⒆詣邮褂梅瓷錂C制姨伤。雖然這又回到了運行時期做事情的老路上,使得性能下降庸疾,但EventBus依然可以正常工作乍楚。
配置
Android Gradle 插件2.2.0之前,我們使用android-apt的方式彼硫,在編譯期生成Subscriber Index類炊豪。所謂APT,即Annotation Processing Tool拧篮,其結(jié)合EventBus提供的EventBusAnnotationProcessor词渤,就可以就在編譯期,對源代碼文件進(jìn)行檢測串绩,找出其中的Annotation缺虐,從而生成目標(biāo)文件,即Subscriber Index類礁凡。
然而高氮,畢竟android-apt是第三方插件慧妄,自Android Gradle 插件2.2.0 之后,其官方提供了相同的能力剪芍,即annotationProcessor 塞淹,來完全代替 android-apt,配置起來更簡單罪裹,推薦大家使用饱普。
1.使用android-apt
buildscript {
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
}
apply plugin: 'com.neenbedankt.android-apt'
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
apt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
apt {
arguments {
eventBusIndex "com.monster.android.wild.MyEventBusIndex"
}
}
com.monster.android.wild.MyEventBusIndex就是我們想要生成的Subscriber Index類
2.使用annotationProcessor
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [eventBusIndex:'com.monster.android.wild.MyEventBusIndex']
}
}
}
}
dependencies {
compile 'org.greenrobot:eventbus:3.0.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
同上,com.monster.android.wild.MyEventBusIndex就是我們想要生成的Subscriber Index類状共√赘可以看到,這樣配置起來更簡單峡继。
3.代碼配置
在build.gradle中配置完畢之后冯袍,再進(jìn)行編譯,Subscriber Index類MyEventBusIndex就會自動為我們生成了碾牌。
但注意康愤,我們的工程中需要有使用EventBus相關(guān)的代碼,才能生成哦舶吗。
接下來翘瓮,在代碼中,如果我們想使用Subscriber Index裤翩,就可以在構(gòu)造EventBus時资盅,如下這樣
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
但如果每次都這樣寫一大串,未免有些繁瑣。既然我們引入了Subscriber Index,我們當(dāng)然希望我們的工程中全部使用這樣方式莽鸭,這時瞬哼,就可以這樣
// 只設(shè)置一次,并且要在我們第一次使用EventBus之前進(jìn)行設(shè)置
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
// 這樣每次獲取到默認(rèn)實例,都是使用Subscriber Index的了,代碼得到了精簡。
EventBus eventBus = EventBus.getDefault();
源代碼
1.Subscriber Index類MyEventBusIndex是如何生成的
首先蓝晒,我們需要明確,MyEventBusIndex是在編譯時期帖鸦,通過第三方提供的android-apt或者Gradle本身提供的annotationProcessor芝薇,結(jié)合EventBus提供的EventBusAnnotationProcessor,共同協(xié)作而生成的作儿。而EventBusAnnotationProcessor由EventBus提供洛二,也就說明具體生成邏輯是由EventBus控制,這算是經(jīng)典的模板模式。
下面晾嘶,我們大致看一下EventBusAnnotationProcessor是如何定義生成邏輯并生成目標(biāo)Java類文件的妓雾。大家可以從EventBus 的Github上面看到完整源代碼,這里只是簡要的闡述:
@SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
@SupportedOptions(value = {"eventBusIndex", "verbose"})
public class EventBusAnnotationProcessor extends AbstractProcessor {
// process()是該類的核心函數(shù)垒迂,就是在這里械姻,實現(xiàn)收集和評估注解的代碼,以及生成Java文件
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
// 保存訂閱者的訂閱方法信息
private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
// 保存需要跳過的訂閱者
private final Set<TypeElement> classesToSkip = new HashSet<>();
try {
// 從build.gradle中讀取到arguments机断,即com.monster.android.wild.MyEventBusIndex
String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
// 收集策添,填充methodsByClass
collectSubscribers(annotations, env, messager);
// 評估,填充classesToSkip
checkForSubscribersToSkip(messager, indexPackage);
if (!methodsByClass.isEmpty()) {
// 生成java文件(根據(jù)methodsByClass和classesToSkip)
createInfoIndexFile(index);
} else {
}
} catch (RuntimeException e) {
}
return true;
}
}
// 生成java文件毫缆,其中有一部分是hardcode
private void createInfoIndexFile(String index) {
BufferedWriter writer = null;
try {
JavaFileObject sourceFile = processingEnv.getFiler().createSourceFile(index);
int period = index.lastIndexOf('.');
String myPackage = period > 0 ? index.substring(0, period) : null;
String clazz = index.substring(period + 1);
writer = new BufferedWriter(sourceFile.openWriter());
if (myPackage != null) {
writer.write("package " + myPackage + ";\n\n");
}
writer.write("import org.greenrobot.eventbus.meta.SimpleSubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberMethodInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfo;\n");
writer.write("import org.greenrobot.eventbus.meta.SubscriberInfoIndex;\n\n");
writer.write("import org.greenrobot.eventbus.ThreadMode;\n\n");
writer.write("import java.util.HashMap;\n");
writer.write("import java.util.Map;\n\n");
writer.write("/** This class is generated by EventBus, do not edit. */\n");
writer.write("public class " + clazz + " implements SubscriberInfoIndex {\n");
writer.write(" private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;\n\n");
writer.write(" static {\n");
writer.write(" SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();\n\n");
writeIndexLines(writer, myPackage);
writer.write(" }\n\n");
writer.write(" private static void putIndex(SubscriberInfo info) {\n");
writer.write(" SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);\n");
writer.write(" }\n\n");
writer.write(" @Override\n");
writer.write(" public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {\n");
writer.write(" SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);\n");
writer.write(" if (info != null) {\n");
writer.write(" return info;\n");
writer.write(" } else {\n");
writer.write(" return null;\n");
writer.write(" }\n");
writer.write(" }\n");
writer.write("}\n");
} catch (IOException e) {
} finally {
}
}
接下來我們看看生成的Subscriber Index類MyEventBusIndex
/** This class is generated by EventBus, do not edit. */
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
// 從以上createInfoIndexFile()的實現(xiàn)來看,除了類名MyEventBusIndex乐导,只有這一段代碼是非hardcode苦丁。
// 以訂閱者類為單位,將該訂閱者類中的所有訂閱函數(shù)以及相關(guān)參數(shù)物臂,封裝到SimpleSubscriberInfo類對象中旺拉,
// 以供EventBus在注冊過程中使用。注意SimpleSubscriberInfo類對象是在編譯時期就生成的棵磷,
// 因而在運行時期可以直接使用蛾狗,省去了運行時期通過反射做相似操作的時間和資源消耗,從而提高效率仪媒,這里就是全文的精髓所在沉桌。
putIndex(new SimpleSubscriberInfo(com.monster.android.wild.myeventbusdemo.MainActivity.class, true,
new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onEvent",
com.monster.android.wild.myeventbusdemo.MainActivity.EventBusEvent.class, ThreadMode.MAIN),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
// 將會在EventBus在注冊過程中使用,等會大家會看到
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
2.EventBus是如何使用Subscriber Index類MyEventBusIndex的
我們知道算吩,在EventBus注冊的時候留凭,需要調(diào)用SubscriberMethodFinder的findSubscriberMethods()方法。忽略無關(guān)代碼如下:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
}
默認(rèn)情況下偎巢,ignoreGeneratedIndex為false蔼夜,所以會調(diào)用findUsingInfo()
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
// 焦點調(diào)用在這里,如何通過索引獲取訂閱者信息
findState.subscriberInfo = getSubscriberInfo(findState);
if (findState.subscriberInfo != null) {
SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
for (SubscriberMethod subscriberMethod : array) {
if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
findState.subscriberMethods.add(subscriberMethod);
}
}
} else {
// 這一點與上文提到的相呼應(yīng):索引找不到压昼,還可以回到老路上求冷,即,使用反射來找窍霞,
// 雖然性能會降低匠题,但保證EventBus仍然可以正常工作
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
順著焦點調(diào)用,我們繼續(xù)往下看
private SubscriberInfo getSubscriberInfo(FindState findState) {
if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) {
SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
if (findState.clazz == superclassInfo.getSubscriberClass()) {
return superclassInfo;
}
}
// 焦點在這里但金,輪詢subscriberInfoIndexes得到SubscriberInfoIndex對象梧躺,從而得到SubscriberInfo對象
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
插一句,那subscriberInfoIndexes如何得來的呢?還記得掠哥,我們在代碼配置的時候調(diào)用過
EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
索引就是這個時候傳給EventBus的巩踏,我們看一下源碼:
/** Adds an index generated by EventBus' annotation preprocessor. */
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if(subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
再回來,所以得到的SubscriberInfoIndex對象就是我們的MyEventBusIndex類對象续搀。接著調(diào)用MyEventBusIndex類對象的getSubscriberInfo()方法塞琼,從而得到SubscriberInfo對象,而這正與MyEventBusIndex源代碼中“等會大家會看到”的注解相呼應(yīng)禁舷!
至此彪杉,代碼邏輯就捋順了~~~
3.總結(jié)
到這里,如何生成Subscriber Index類以及如何使用Subscriber Index類就分析完畢了牵咙。分析過程中派近,大家應(yīng)該就可以看到了,核心思想就是把運行時所需要做的耗時操作轉(zhuǎn)移到了編譯時期去做洁桌,從而提高了整體性能渴丸。