前回の記事Android EventBus の使い方理解最後に、リフレクション検索のサブスクリプション方式が効率に影響を与えるのではないかと思い、APP 製品では Subscriber Index を使用することが推奨されていると公式 Web サイトで見ました。理由としては、高速化とクラッシュの回避が挙げられます。
サブスクライバー インデックスを使用すると、実行時にリフレクションを使用して高価なサブスクリプション メソッドの検索を回避できます。アノテーション プロセッサを使用して、コンパイル時にサブスクリプション メソッドを検索します。
本当にすごいですね!実行時のオーバーヘッドはコンパイル時に行われます。
要件を満たす
- サブスクリプション メソッドとサブスクライバ クラスはパブリックである必要があります
- イベントクラスはパブリックである必要があります
- サブスクリプション メソッドを匿名クラスに配置することはできません
インデックスを生成する方法
Java言語の場合はアノテーションプロセッサを使用します。Kotlin を使用する場合は Kapt を使用してください。
ここでは主に Java を使用した設定方法について説明します。私の build.gradle.kts は Kotlin スクリプトなので、設定を投稿します。
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions.arguments["eventBusIndex"] = "com.example.testeventbus.MyEventBusIndex"
}
}
}
dependencies {
val eventbus_version = "3.3.1"
implementation("org.greenrobot:eventbus:$eventbus_version")
annotationProcessor("org.greenrobot:eventbus-annotation-processor:$eventbus_version")
}
インデックスの使い方
まずプロジェクトをビルドする必要があります。設定したように、com.example.testeventbus.MyEventBusIndex クラスが自動的に生成されます。
コードが初期化される場合、EventBus.getDefault() を呼び出す前に、EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus() を呼び出す必要があります。
それだけです。他に何もする必要はありません。
コードを結合してインデックスを分析する
前回の記事 でのAndroid EventBus の使用例と理解に基づいて分析してみましょう。
これは主に、現在のクエリ サブスクリプション メソッドのロジックを分析するためです。
追加されたクラス
アノテーションを使用して自動的に生成されたクラスは com.example.testeventbus.MyEventBusIndex です。その実装を見てください。
public class MyEventBusIndex implements SubscriberInfoIndex {
private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
static {
SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
putIndex(new SimpleSubscriberInfo(MainActivity.class, true, new SubscriberMethodInfo[] {
new SubscriberMethodInfo("onMessageEvent", MessageEvent.class, ThreadMode.MAIN),
new SubscriberMethodInfo("onMessage", MessageEvent.class, ThreadMode.MAIN),
}));
}
private static void putIndex(SubscriberInfo info) {
SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
}
@Override
public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
if (info != null) {
return info;
} else {
return null;
}
}
}
このクラスは、SubscriberInfoIndex を継承し、getSubscriberInfo(Class<?> subscriberClass) メソッドを実装します。同時に、MyEventBusIndex クラスの静的コード ブロックで、サブスクリプション メソッドの関連情報が静的メンバー SUBSCRIBER_INDEX に入れられていることがわかります。 。
この例では、SUBSCRIBER_INDEX のキーは MainActivity.class で、MainActivity はサブスクライバ オブジェクトです。SUBSCRIBER_INDEX は実際にはサブスクライバ オブジェクトの Class をキーとして使用し、値は SubscriberInfo 実装クラスです。ここで、その値は SimpleSubscriberInfo クラス オブジェクトです。
SimpleSubscriberInfo によって初期化される 3 番目のパラメーターは SubscriberMethodInfo の配列であり、SubscriberMethodInfo はサブスクリプション メソッドを記述するために使用されます。この例では 2 つのメソッドを宣言したため、2 つの配列が存在します。Android EventBus の使い方の理解では、リフレクションを通じてこの 2 つのメソッドを見つけましたが、今回はこれら 2 つのメソッドを直接見つけました。これが、公式 Web サイトで実行時検索とコンパイル時検索の意味です。
生成されたインデックスを EventBus.getDefault() に配置します。
呼び出すたびに EventBus.getDefault() を通じて関連する操作を実装するため、それが機能するには、生成されたインデックスを EventBus.getDefault() に入れる必要があります。これは、EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus() を通じて行われます。
まず EventBus.builder().addIndex(new MyEventBusIndex()) を見てみましょう。
/** Adds an index generated by EventBus' annotation preprocessor. */
public EventBusBuilder addIndex(SubscriberInfoIndex index) {
if (subscriberInfoIndexes == null) {
subscriberInfoIndexes = new ArrayList<>();
}
subscriberInfoIndexes.add(index);
return this;
}
新しく生成された MyEventBusIndex がメンバー変数subscriberInfoIndexesに追加されていることがわかります。subscriberInfoIndexes のタイプは ArrayList です。
installDefaultEventBus() メソッドをもう一度見てください。
/**
* Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
* done only once before the first usage of the default EventBus.
*
* @throws EventBusException if there's already a default EventBus instance in place
*/
public EventBus installDefaultEventBus() {
synchronized (EventBus.class) {
if (EventBus.defaultInstance != null) {
throw new EventBusException("Default instance already exists." +
" It may be only set once before it's used the first time to ensure consistent behavior.");
}
EventBus.defaultInstance = build();
return EventBus.defaultInstance;
}
}
/** Builds an EventBus based on the current configuration. */
public EventBus build() {
return new EventBus(this);
}
build() で直接 EventBus.defaultInstance を生成します。後で EventBus.getDefault() を呼び出して、EventBus.defaultInstance を取得します。
EventBus はインデックスに関するコードを初期化します。
EventBus(EventBusBuilder builder) {
logger = builder.getLogger();
subscriptionsByEventType = new HashMap<>();
typesBySubscriber = new HashMap<>();
stickyEvents = new ConcurrentHashMap<>();
mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);
indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
builder.strictMethodVerification, builder.ignoreGeneratedIndex);
logSubscriberExceptions = builder.logSubscriberExceptions;
logNoSubscriberMessages = builder.logNoSubscriberMessages;
sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
throwSubscriberException = builder.throwSubscriberException;
eventInheritance = builder.eventInheritance;
executorService = builder.executorService;
}
重要なのは、indexCount とsubscriberMethodFinder に関する真ん中の 2 つの文です。Index コレクションを SubscriberMethodFinder クラス オブジェクトsubscriberMethodFinderに設定します。SubscriberMethodFinder がサブスクリプション メソッドの検索に関するものであることはわかっています。検索する場合、subscriberInfoIndexes に関連します。インデックスが以前に設定されていない場合、インデックスは空でスキップされます。値が設定されたので、サブスクリプション メソッドを見つけるロジックをもう一度見てみましょう。
購読する方法を見つける
最初から始める必要はなく、関連するコードにジャンプするだけです。subscriberMethodFinder クラスの findUsingInfo(Class<?> subscriberClass) だけです。方法:
private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
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 {
findUsingReflectionInSingleClass(findState);
}
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
重要な変更は getSubscriberInfo(findState) セクションにあります。この部分によって返される findState.subscriberInfo は以前は空だったので、findUsingReflectionInSingleClass(findState) が使用されます。現在は空ではないので、リフレクション検索メソッドを使用する必要はありません。 。
ここで getSubscriberInfo(findState) を見てみましょう。
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;
}
}
if (subscriberInfoIndexes != null) {
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
}
return null;
}
現時点では、findState.subscriberInfo は null ですが、値を割り当てたため、subscriberInfoIndexes は null ではなくなります。この例では、subscriberInfoIndexes に MyEventBusIndex オブジェクトがあります。次に、その getSubscriberInfo(Class<?> subscriberClass) メソッドを呼び出します。SimpleSubscriberInfo オブジェクトを返します。その後、オブジェクトが返されます。findUsingInfo()
メソッド に戻り、SimpleSubscriberInfo の getSubscriberMethods() を呼び出します。次のように:
@Override
public synchronized SubscriberMethod[] getSubscriberMethods() {
int length = methodInfos.length;
SubscriberMethod[] methods = new SubscriberMethod[length];
for (int i = 0; i < length; i++) {
SubscriberMethodInfo info = methodInfos[i];
methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
info.priority, info.sticky);
}
return methods;
}
SimpleSubscriberInfo には 2 つの SubscriberMethodInfo が存在し、SubscriberMethodInfo を SubscriberMethod に変換する必要があります。これは、createSubscriberMethod() を呼び出すことで実現されます。
protected SubscriberMethod createSubscriberMethod(String methodName, Class<?> eventType, ThreadMode threadMode,
int priority, boolean sticky) {
try {
Method method = subscriberClass.getDeclaredMethod(methodName, eventType);
return new SubscriberMethod(method, eventType, threadMode, priority, sticky);
} catch (NoSuchMethodException e) {
throw new EventBusException("Could not find subscriber method in " + subscriberClass +
". Maybe a missing ProGuard rule?", e);
}
}
SubscriberClass はサブスクライバ クラス オブジェクトの Class で、ここでは getDeclaredMethod() によって対応する Method を取得し、SubscriberMethod オブジェクトを生成します。
このようにして、findUsingInfo()メソッドに戻り、再度 findState.checkAdd() を通じて結果セットにサブスクリプション メソッドを追加できるかどうかの判断を開始します (この判断ロジックは、前の記事Android EventBus の使い方を理解するにあります)。
これで、リフレクションを通じてサブスクリプションを見つける方法を Index がどのように置き換えるかが理解できました。