イベントバスのメリット
1. コンポーネント間の対話を簡素化する
イベントの送信者と受信者を
アクティビティ、フラグメント、バックグラウンド スレッドから切り離す
複雑でエラーが発生しやすいライブラリやライフサイクルの問題を回避するためにうまく
活用する 2. コードをより簡潔にする 3.
高速である
4. ファイルが非常に小さい (jar パッケージは約 60k) 5.
配信スレッド、加入者優先度などの高度な機能を備えています
6. 実際の使用では、APP は 10 億回以上インストールされています。
次に、例を使用してその使用法を確認します。
使用例
ここではアクティビティとフラグメントを例に、EventBus の使い方を簡単に見てみましょう。
MainActivity のコードは次のとおりです。
public class MainActivity extends FragmentActivity {
public static final String TAG = "MainActivity";
private TextView main_tv;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_ac);
main_tv = findViewById(R.id.main_tv);
FragmentManager fmanager = getSupportFragmentManager();
FragmentTransaction trac = fmanager.beginTransaction();
trac.add(R.id.main_fl, new BusFragment());
trac.commit();
EventBus.getDefault().register(this);
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent met) {
main_tv.setText(met.toString());
Log.e(TAG, "onMessageEvent接收到消息:" + met);
}
@Override
protected void onDestroy() {
EventBus.getDefault().unregister(this);
super.onDestroy();
}
}
主にテキストコントロールTextView main_tvがあり、BusFragmentが追加されています。
onCreate() で登録されたサブスクライバを実行します。サブスクライバは MainActivity です。onDestroy()で登録解除を実行します。
同時に、@Subscribe アノテーションを使用して受信メソッドとイベントを宣言します。
イベントはクラスによって定義されます。ここでは MessageEvent を示します
public class MessageEvent {
int count;
String con;
@Override
public String toString() {
return "MessageEvent{" +
"count=" + count +
", con='" + con + '\'' +
'}';
}
}
イベント クラスには 2 つの変数 count と con があります。
BusFragment のコードをもう一度見てください。
public class BusFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.bus_frag, container, false);
Button frag_btn = view.findViewById(R.id.frag_btn);
frag_btn.setOnClickListener(itview ->{
MessageEvent et = new MessageEvent();
et.count = 10;
et.con = "测试内容";
EventBus.getDefault().post(et);
});
return view;
}
}
BusFragment に frag_btn というボタンがあり、クリックするとイベントクラスが割り当てられ、EventBus.getDefault().post(et) が呼び出されてイベントが発行されます。
このようにして、イベントを MainActivity に発行でき、MainActivity は onMessageEvent() でイベント メッセージを受信できます。
このようにして、Activity と Fragment 間の相互作用を実現できます。コードから判断すると、比較的単純です。
ソースコード分析
以下は、サブスクライバーの登録、イベントの公開、サブスクライバーの登録解除の 3 つの側面からの分析です。
購読者の登録
呼び出しは EventBus.getDefault().register(this) で、これはサブスクライバである MainActivity です。
まず EventBus.getDefault() を呼び出してデフォルトの EventBus オブジェクトを生成し、次に登録されたサブスクライバー register(this) を呼び出します。
デフォルトの EventBus オブジェクトを生成する
EventBus のメンバー変数では、次のように重要なものをいくつか取り上げます。
StickyEvents: スティッキー イベントに関連しており、キーはスティッキー イベント クラスの Class、値はスティッキー イベント クラス オブジェクトです。
mainThreadSupport: 現在実行中のスレッドがメイン スレッドにあるかどうかを判断するために使用されます
mainThreadPoster: 主に、threadMode が MAIN または MAIN_ORDERED であるサブスクリプション メソッドの実行を処理し、実行のためにメイン スレッドに置くために使用されます。
backgroundPoster: 主に、threadMode が Background であるサブスクリプション メソッドの実行を処理するために使用されます。サブスクリプション メソッドが実行されるときに、サブスクリプション メソッドがメイン スレッドにあることが判明すると、backgroundPoster は現在のバックグラウンド スレッドでサブスクリプション メソッドを実行します。 。
asyncPoster: これは主に、threadMode が ASYNC であるサブスクリプション メソッドの実行を処理するために使用されます。ここで主に強調されているのは非同期です。つまり、サブスクリプション メソッドが実行されるときに、メソッドを実行するために新しいスレッドが開始される必要があります。
subscriberMethodFinder: 名前が示すように、サブスクリプション メソッドのファインダー。そう、このクラスにある subscription メソッドです。詳細は後述する。
executorService: スレッド プール。デフォルトは Executors.newCachedThreadPool() です。
throwSubscriberException: デフォルトは false です。サブスクリプション メソッドの実行時に例外が発生した場合に、例外がスローされるかどうか。
sendSubscriberExceptionEvent: デフォルトは true です。サブスクリプション メソッドの実行時に例外が発生した場合に、SubscriberExceptionEvent イベントを送信するかどうか。
sendNoSubscriberEvent: デフォルトは true です。イベントを発行するときに、対応するサブスクリプション メソッドが見つからない場合に、NoSubscriberEvent イベントを発行するかどうか。
eventInheritance: イベントを公開するとき、イベントはそれ自体のタイプを公開するだけでなく、継承するすべてのインターフェイスとクラス イベント タイプも公開します。デフォルトは true です。スティッキー イベントをサブスクライブすると、それ自体とそのサブクラスのイベントが検索され、公開されます。
EventBus の構築には、EventBusBuilder を使用するビルダー パターンが使用されます。EventBus.getDefault() を見てください。
static volatile EventBus defaultInstance;
…………
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
一般的なシングルトンは、defaultInstance が最初は null であるため、二重チェックを生成し、EventBus の初期化関数に進みます。
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
public EventBus() {
this(DEFAULT_BUILDER);
}
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;
}
DEFAULT_BUILDER は EventBus クラスの静的変数です。見てわかるように、ここでは従来の build() メソッドは使用されていませんが、EventBus オブジェクトの生成には割り当てメソッドが使用されています。各変数については、クラス オブジェクトのメンバー変数を導入するときに上で説明しました。
購読者の登録
EventBusのレジスタ(オブジェクトサブスクライバ)を見てみましょう方法:
public void register(Object subscriber) {
if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
// Crash if the user (developer) has not imported the Android compatibility library.
throw new RuntimeException("It looks like you are using EventBus on Android, " +
"make sure to add the \"eventbus\" Android library to your dependencies.");
}
Class<?> subscriberClass = subscriber.getClass();
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
パラメーター サブスクライバーはサブスクライバーであり、上の例では MainActivity インスタンスです。
まずサブスクライバーのクラスを取得し、次に EventBus メンバー変数subscriberMethodFinder の findSubscriberMethods() を呼び出して、対応するクラスのサブスクリプション メソッド (@Subscribe でマークされたメソッド) を取得します。
ループ内でsubscribe(subscriber,subscriberMethod)メソッドを呼び出して、サブスクライバをsubscriberMethodにサブスクライブします。実際には、subscriber と subscriberMethod を対応するデータ構造 (主に subscriptionsByEventType と tablesBySubscriber) に配置し、サブスクリプション イベントが発行されるのを待機するときに、関連するデータ構造に移動して関連情報を取得し、サブスクリプション メソッドを実行します。
サブスクライバによって実装されたサブスクリプション メソッドを取得します
これは主に、subscriberMethodFinder.findSubscriberMethods(subscriberClass) を呼び出すことによって取得されます。subscriberMethodFinder は SubscriberMethodFinder クラス オブジェクトであり、パラメータsubscriberClassは上記で取得したサブスクライバのクラスです。コードの実行方法は次のとおりです。
ここで、特定の SubscriberMethodFinder クラスの findSubscriberMethods() メソッドを見てみましょう。
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
if (ignoreGeneratedIndex) {
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
パラメータsubscriberClassは、サブスクライバ・オブジェクトのクラスです。サブスクライバ・オブジェクトでは複数のサブスクリプション・メソッドが許可されるため、検索の結果はコレクションのリストになります。METHOD_CACHE は、キャッシュとして使用される ConcurrentHashMap<Class<?>, List> オブジェクトです。以前に検索されたことがあれば結果が保存され、キー値はサブスクライバー オブジェクトのクラスです。したがって、キャッシュからのクエリ後に対応する結果が null でない場合は、その結果が直接返されます。
デフォルトでは、ignoreGeneratedIndex は false なので、findUsingInfo(subscriberClass) を呼び出して結果を取得します。
最後に、結果は METHOD_CACHE に格納されます。
findUsingInfo(subscriberClass) を調べる前に、次のように FindState クラス構造を理解する必要があります。
anyMethodByEventType: 検索処理時の判定に使用され、キーはイベントタイプのClass、値はMethodまたはFindStateオブジェクトです。
検索時の判定にもsubscriberClassByMethodKeyが使用されます。キーはメソッド名+">"+イベントタイプ名、値はメソッドのライフクラスのClass(サブスクリプションクラスのClass)です。
MethodKeyBuilder:subscriberClassByMethodKey のキー値を綴るのに使用されます
subscriberClass:サブスクライバ クラスのクラス
clazz:最初のサブスクライバ クラスのクラス、後でその親クラスに再帰します
SkipSuperClasses: メソッドの検索をスキップするかどうか親クラスのクラス
submitrInfo : サブスクライバ情報 (現在は null)
FindState クラスもキャッシュ メカニズムを使用します。その実装は SubscriberMethodFinder の FIND_STATE_POOL です。これはサイズ 4 の FindState 配列です。
これで、引き続き findUsingInfo() を確認できます。方法。
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);
}
prepareFindState() メソッドは、最初に FIND_STATE_POOL 内のオブジェクトを取得し、そうでない場合はオブジェクトを再生成します。
findState.initForSubscriber(subscriberClass)は、サブスクライバのクラスとしてfindStateのsubscriberClassとclazzを設定します。SkipSuperClasses は false で、subscriberInfo は null です。
findState.subscriberInfo が null なので、現在の findState.clazz が null ではないため、次のコードは while ループに入ります。findUsingReflectionInSingleClass() が終了すると、親クラスにトラバースして実行を続けます。スーパークラス Class への移行は、findState.moveToSuperclass() によって実現されます。
最後に、getMethodsAndRelease(findState) が呼び出されて、findState オブジェクトの subscriberMethods メンバーに格納されている結果を取得し、findState をキャッシュ プール FIND_STATE_POOL に置きます。
ここでの主な処理は findUsingReflectionInSingleClass(findState) です。これを読むと、EventBus がリフレクションを通じて検索サブスクリプション メソッドをどのように実装しているかを知ることができます。
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// This is faster than getMethods, especially when subscribers are fat classes like Activities
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
try {
methods = findState.clazz.getMethods();
} catch (LinkageError error) {
// super class of NoClassDefFoundError to be a bit more broad...
String msg = "Could not inspect methods of " + findState.clazz.getName();
if (ignoreGeneratedIndex) {
msg += ". Please consider using EventBus annotation processor to avoid reflection.";
} else {
msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
}
throw new EventBusException(msg, error);
}
findState.skipSuperClasses = true;
}
for (Method method : methods) {
int modifiers = method.getModifiers();
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
if (findState.checkAdd(method, eventType)) {
ThreadMode threadMode = subscribeAnnotation.threadMode();
findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
}
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException("@Subscribe method " + methodName +
"must have exactly 1 parameter but has " + parameterTypes.length);
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
現在、findState.clazz はサブスクライバーのクラスであり、上記の例の MainActivity のクラスに対応します。リフレクション メカニズムを通じて、その getDeclaredMethods() メソッドを呼び出して、独自のクラスによって宣言されたすべてのメソッドを取得します。このメソッドで例外が発生した場合、getMethods() が呼び出されます。getMethods() は、スーパークラスおよびスーパーインターフェイスから継承された含まれるパブリック メソッドを返し、skipSuperClasses を true に設定することに注意してください。すべてのメソッドが取得されているため、親クラスにトラバースする必要はありません。
次に、クエリされたメソッドをループします。
まずメソッドの修飾子を決定します。サブスクリプション メソッドを public に設定する必要があることがわかります。また、Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC の 4 つのモディファイアを使用することはできません。Modifier.ABSTRACT | Modifier.STATIC 抽象メソッドと静的メソッドの 2 つがわかります。BRIDGE | SYNTHETIC はジェネリック クラスから継承された具象型で、継承されたジェネリック クラスにジェネリック メソッドが含まれる場合、そのメソッドは BRIDGE | SYNTHETIC でマークされます。ここで、このクラスの対応するメソッドがサブスクリプション メソッドが機能しないことを宣言していることを知る必要があります。
サブスクリプション メソッドのパラメータは 1 つだけであり、Subscribe アノテーションが必要です。strictMethodVerification はデフォルトでは false ですが、true に設定すると、例外 EventBusException が報告されます。
eventsType は、メソッド パラメーターのクラス、つまり、サブスクライブされたイベント クラスのクラスです。findState.checkAdd(method,eventType)を通じて、findState.subscriberMethodsにメソッドと関連情報を追加できるかどうかを判断します。SubscriberMethodクラスは、サブスクリプションメソッドを記述します。そのメンバーには、実行メソッド Method、イベント タイプ、Subscribe にアノテーションを付けることで取得される threadMode、priority、sticky が含まれます。
どのような状況でメソッドを追加できるかを確認するために、findState.checkAdd(method,eventType) を見てみましょう。
boolean checkAdd(Method method, Class<?> eventType) {
// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
// Usually a subscriber doesn't have methods listening to the same event type.
Object existing = anyMethodByEventType.put(eventType, method);
if (existing == null) {
return true;
} else {
if (existing instanceof Method) {
if (!checkAddWithMethodSignature((Method) existing, eventType)) {
// Paranoia check
throw new IllegalStateException();
}
// Put any non-Method object to "consume" the existing Method
anyMethodByEventType.put(eventType, this);
}
return checkAddWithMethodSignature(method, eventType);
}
}
新しいイベント タイプが追加されるたびに、それを追加する必要があることがわかります。anyMethodByEventType のキーに対応する値が null であるためです。
同じ種類のイベントが 2 回目に追加された場合はどうなりますか? Method メソッド名が前に追加したメソッド名と同じかどうかの 2 つのケースがあります。また、同じイベントタイプを 2 回目に追加する場合、checkAddWithMethodSignature() メソッドが 2 回実行されることがわかります。2つの場合に分けて議論する
1. メソッド名が以前とは異なります。
以前に追加されたメソッドはanyMethodByEventType.put(eventType, method)を通じて取得されます。メソッドなので最初のcheckAddWithMethodSignature()メソッドに入って判定します。
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
methodKeyBuilder.setLength(0);
methodKeyBuilder.append(method.getName());
methodKeyBuilder.append('>').append(eventType.getName());
String methodKey = methodKeyBuilder.toString();
Class<?> methodClass = method.getDeclaringClass();
Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
// Only add if not already found in a sub class
return true;
} else {
// Revert the put, old class is further down the class hierarchy
subscriberClassByMethodKey.put(methodKey, methodClassOld);
return false;
}
}
ご覧のとおり、キー値は fangfaming + ">" + イベント クラス名がキー値になります。宣言されたメソッドの Class を取得し、subscriberClassByMethodKey に追加します。このキー値を初めて追加するため、取得されたメソッドClassOld は null になり、直接 true を返します。ここで明確にしておきたいのですが、メソッドは初めてイベントを追加するときのメソッドです。
checkAddWithMethodSignature() の最初の実行では true が返され、その後、anyMethodByEventType の値が FindState クラス オブジェクトに更新されます。それから二回目に行きます。今回はメソッドを追加するメソッドです。再度checkAddWithMethodSignature()メソッドに行きます。メソッド名が違うのでキー値が生成されます。subscriberClassByMethodKeyにはまだ対応する値がありません。追加後、methodClassOldはnullのままですが、したがって、trueを返します。
このとき、結果に購読メソッドを追加することができます。
2. メソッド名は前回と同じですが、
今回と最初のケースの違いは、2パス目でcheckAddWithMethodSignature()を実行すると、1パス目の状況が同じになることです。
2 番目の checkAddWithMethodSignature はキーの値を生成しますが、メソッド名とイベント名が同じであるため、subscriberClassByMethodKey.put() の場合、返される結果は null ではありません。これは、メソッドが最初に追加されるときのメソッドの宣言クラスです。次に、methodClassOld.isAssignableFrom(methodClass)で判定します。methodClassOldは、初めてメソッドを追加したときのメソッドの宣言クラスです。MethodClass は 2 回目に追加するメソッドの宣言クラスです。
MethodClassOld.isAssignableFrom(methodClass) は、methodClassOld が親クラス、インターフェイス クラス、またはメソッドクラスと等しいと判断された場合に true を返します。つまり、初めて追加したこのタイプのイベントのクラスが、親クラス、インターフェースクラス、または 2 番目に追加したタイプのクラスと等しい場合、true が返されます。
どのような場合に、同じメソッド名とイベントタイプのサブスクリプションメソッドが追加されるのかをもう一度考えてみましょう。確かに同じクラス内にはなく、サブクラスと親クラスの両方で同じメソッド名と同じイベントが定義された関数のみが存在します。findUsingInfo()を通じて、イベントが最初にサブクラスから追加されることがわかるため、このタイプのイベントを初めて追加するクラスは、2 番目のタイプを追加するクラスのサブクラスになります。 、いいえを通過する必要があります。したがって、FindState クラスのメンバーsubscriberClassByMethodKey が主にこの状況を防ぐためのものであることがわかります。
このように、register(Object subscriber)メソッドに戻るsubscriberMethodFinder.findSubscriberMethods(subscriberClass)が実行され、検索結果はすべてsubscriberMethodsに収まります。
定期購入の申し込み方法
対応する submit() メソッドのコードを見てください。コードは少し長いので、いくつかのセクションに分けて見てみましょう。
// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
主にサブスクライバとサブスクリプション オブジェクトを Subscription にカプセル化し、それを subscriptionsByEventType に置きます。
Mr. サブスクライバーsubscriberとサブスクリプションメソッドsubscriberMethodを含むSubscriptionオブジェクトnewSubscriptionを生成します。subscriptionsByEventType は、サブスクリプション イベントのタイプをキーとして使用します。空の場合は、新しいイベントが作成され、subscriptionsByEventType に設定されます。
その後、優先順位のサイズに従って場所が検出されます。優先順位が高いほど、順位が高くなります。
もう一度、subscribe() メソッドの 2 番目のコードを見てください。
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
subscribedEvents.add(eventType);
このコードは非常に単純で、サブスクライバによってサブスクライブされたイベント タイプを typeBySubscriber に置きます。
subscribe() メソッドの最後のコードを見てみましょう。
if (subscriberMethod.sticky) {
if (eventInheritance) {
// Existing sticky events of all subclasses of eventType have to be considered.
// Note: Iterating over all events may be inefficient with lots of sticky events,
// thus data structure should be changed to allow a more efficient lookup
// (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
現在のサブスクリプション メソッドがスティッキーである場合、stickyEvents 内のイベントがチェックされ、それらがサブクラスであるか、現在のサブスクリプション メソッドのイベント タイプに類似している場合、イベントが発行されます。スティッキーイベントを公開し、サブスクライブした後、該当イベントを受信することも可能になります。
EventInheritance と isAssignableFrom() メソッドについては前述しました。イベント タイプが現在バインドされているeventType、またはそのサブクラスと同じ場合、checkPostStickyEventToSubscription(newSubscription, StickyEvent) が呼び出され、リリースが実行されます。見てみましょう:
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
// If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
// --> Strange corner case, which we don't take care of here.
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
Sticky イベントが null でない場合は、postToSubscription(newSubscription, StickyEvent, isMainThread()) を実行してリリースを実行します。リリースの内容は以下のとおりです。
イベント後
イベントを投稿するには、EventBus.getDefault().post(Object event) を呼び出します。
/** Posts the given event to the event bus. */
public void post(Object event) {
PostingThreadState postingState = currentPostingThreadState.get();
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
currentPostingThreadState はスレッドに関連しており、get() を呼び出すと PostingThreadState クラスが取得されます。そのメンバーのeventQueueはArrayList型であり、イベントキューとして使用されます。ここでイベントはキューに入れられます。
postingState.isPosting は、イベントが送信されているかどうかを表します。postingState.isPosting が false の場合、メッセージは以下に送信され、postingState.isPosting は true に設定されます。postedState.isMainThread には isMainThread() が割り当てられ、現在のスレッドがメイン スレッドかどうかを示します。
次に、ループを開始して postSingleEvent() を呼び出し、キュー内のイベントを送信します。最後に、postingState.isPosting = false、postingState.isMainThread = false を設定します。
メインスレッドにあるのか
isMainThread() を見て、メインスレッドがどのように決定されるかを確認してください。
private boolean isMainThread() {
return mainThreadSupport == null || mainThreadSupport.isMainThread();
}
mainThreadSupport が null または mainThreadSupport.isMainThread() の場合。ここで mainThreadSupport が EventBusBuilder で定義されています。見てください。
MainThreadSupport getMainThreadSupport() {
if (mainThreadSupport != null) {
return mainThreadSupport;
} else if (AndroidComponents.areAvailable()) {
return AndroidComponents.get().defaultMainThreadSupport;
} else {
return null;
}
}
これは主に AndroidComponents.get().defaultMainThreadSupport を通じて取得されていることがわかります。AndroidComponents.get() は次のとおりです。
private static final AndroidComponents implementation;
static {
implementation = AndroidDependenciesDetector.isAndroidSDKAvailable()
? AndroidDependenciesDetector.instantiateAndroidComponents()
: null;
}
public static boolean areAvailable() {
return implementation != null;
}
public static AndroidComponents get() {
return implementation;
}
public final Logger logger;
public final MainThreadSupport defaultMainThreadSupport;
public AndroidComponents(Logger logger, MainThreadSupport defaultMainThreadSupport) {
this.logger = logger;
this.defaultMainThreadSupport = defaultMainThreadSupport;
}
実装が AndroidDependencyDetector.instantiateAndroidComponents() によって取得されていることがわかります。
private static final String ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME = "org.greenrobot.eventbus.android.AndroidComponentsImpl";
public static AndroidComponents instantiateAndroidComponents() {
try {
Class<?> impl = Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME);
return (AndroidComponents) impl.getConstructor().newInstance();
}
catch (Throwable ex) {
return null;
}
}
実際の初期化は AndroidComponentsImpl クラスであることがわかります。
public class AndroidComponentsImpl extends AndroidComponents {
public AndroidComponentsImpl() {
super(new AndroidLogger("EventBus"), new DefaultAndroidMainThreadSupport());
}
}
そのdefaultMainThreadSupportがDefaultAndroidMainThreadSupportクラスオブジェクトであることがわかります。
したがって、EventBus オブジェクトの mainThreadSupport は、DefaultAndroidMainThreadSupport クラス オブジェクトです。。
isMainThread() を見てください。
@Override
public boolean isMainThread() {
return Looper.getMainLooper() == Looper.myLooper();
}
現在のスレッドの Looper がメイン スレッドの Looper.getMainLooper() と等しいかどうかを確認します。等しい場合はメイン スレッド内にあり、そうでない場合はメイン スレッドではありません。
単一のイベントを公開する
postSingleEvent(Objectevent,PostingThreadStatepostingState)の関連コードを見てください。
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
eventClass イベントのクラス型。subscriptionFound は、イベントが公開されているかどうかを表します。ここでは、eventInheritance の機能を確認できます。これは、lookupAllEventTypes(eventClass) を呼び出して、独自のクラスのクラスと、継承された親クラスおよびインターフェイスの関連クラスを検索します。これらの一致するイベント オブジェクトが見つかる限り、それらは呼び出されます。
次に、見つかったすべてのイベント タイプに対して postSingleEventForEventType(event,postingState,clazz) を呼び出します。そのコードを見てみましょう。
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted;
try {
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
実行する必要があるサブスクリプション メソッドに関する情報を subscriptionsByEventType から取得します。見つかった場合は、サブスクリプションを走査してサブスクリプションを取得し、引き続き postToSubscription(subscription,event,postingState.isMainThread) を呼び出してメッセージを送信します。
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
ここからはサブスクリプションの方法について説明していきます。処理の前に、スレッド スケジューリングが生成されるかどうかを確認する必要もあります。たとえば、ここで設定されている threadMode は MAIN で、これはメインスレッドで実行する必要があることを意味しますが、呼び出しスレッドがメインスレッドにあるかどうかは以前に判断しましたが、変数 isMainThread によって制御されます。isMainThread が true の場合、invokeSubscriber(subscription,event) が直接呼び出され、isMainThread が false の場合、mainThreadPoster.enqueue(subscription,event) が呼び出されます。
スレッドのスケジュール設定
メインスレッドの呼び出しを構成することを選択して、実行スケジューリングをどのように実現するかについて説明しましょう。
まず、現在メインスレッドにあり、スレッド スケジューリングを生成しない場合は、 invokeSubscriber(subscription,event) を直接実行します。
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
Methodのinvokeメソッドを直接呼び出してメソッドの実行を実装します。subscription.subscriber はサブスクライバ オブジェクト、event はイベント、subscription.subscriberMethod.method はサブスクリプション メソッドです。
現在メインスレッドにないことを確認し、mainThreadPoster.enqueue(subscription,event) を呼び出します。mainThreadPoster は mainThreadSupport.createPoster(this) によって生成されます。mainThreadSupport がDefaultAndroidMainThreadSupport クラス オブジェクトであることがわかります。DefaultAndroidMainThreadSupportd の CreatePoster(this) を見てください。
@Override
public Poster createPoster(EventBus eventBus) {
return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);
}
したがって、mainThreadPoster は HandlerPoster オブジェクトです。HandlerPoster のエンキュー (サブスクリプション、イベント) を見てみましょう。
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!handlerActive) {
handlerActive = true;
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
}
}
}
HandlerPoster は Handler クラスを継承し、通話情報とイベントを PendingPost オブジェクトにカプセル化し、キューに入れます。次に、sendMessage(obtainMessage()) を呼び出してここにメッセージを送信し、Android のメッセージング メカニズムを通じてスレッド スケジューリングを実装します。
HandlerPoster の handleMessage(Message msg) を見てください。
@Override
public void handleMessage(Message msg) {
boolean rescheduled = false;
try {
long started = SystemClock.uptimeMillis();
while (true) {
PendingPost pendingPost = queue.poll();
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
handlerActive = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
long timeInMethod = SystemClock.uptimeMillis() - started;
if (timeInMethod >= maxMillisInsideHandleMessage) {
if (!sendMessage(obtainMessage())) {
throw new EventBusException("Could not send handler message");
}
rescheduled = true;
return;
}
}
} finally {
handlerActive = rescheduled;
}
}
}
ご覧のとおり、キューから PendingPost オブジェクトを取得し、eventBus.invokeSubscriber(pendingPost) を呼び出して実行します。
void invokeSubscriber(PendingPost pendingPost) {
Object event = pendingPost.event;
Subscription subscription = pendingPost.subscription;
PendingPost.releasePendingPost(pendingPost);
if (subscription.active) {
invokeSubscriber(subscription, event);
}
}
void invokeSubscriber(Subscription subscription, Object event) {
try {
subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
} catch (InvocationTargetException e) {
handleSubscriberException(subscription, event, e.getCause());
} catch (IllegalAccessException e) {
throw new IllegalStateException("Unexpected exception", e);
}
}
Subscription オブジェクトの active が true (アクティブであることを意味する) であることを確認し、その subscription メソッドを実行します。最後に、メソッドの invoke() も実行され、サブスクリプション メソッドの呼び出しが実行されます。
他のスレッド モードの切り替えは、EventBus オブジェクトのスレッド プールを呼び出すことで実装されるため、ここでは詳しく説明しません。
加入者の登録を解除する
Unsubscriber は、このイベントをサブスクライブしたくないことを意味します。このとき、EventBus.getDefault().unregister(this) メソッドを実行する必要があります。そのコードを見てみましょう。
/** Unregisters the given subscriber from all event classes. */
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
if (subscriptions != null) {
int size = subscriptions.size();
for (int i = 0; i < size; i++) {
Subscription subscription = subscriptions.get(i);
if (subscription.subscriber == subscriber) {
subscription.active = false;
subscriptions.remove(i);
i--;
size--;
}
}
}
}
主に、typesBySubscriber と subscriptionsByEventType を扱います。
サブスクライバーがサブスクライブしているイベントのタイプを typeBySubscriber を通じて取得し、すべてのイベント タイプを走査して、unsubscribeByEventType(subscriber,eventType) を呼び出して subscriptionsByEventType 内のサブスクリプション イベント情報を処理します。トラバーサルが完了すると、typesBySubscriber はサブスクライバーを削除します。
unsubscribeByEventType(subscriber,eventType) は、購読解除者に一致する購読情報クラス Subscription も検索します。subscription.active = false を削除します。
要約する
フレームワーク コード全体を分析すると、EventBus はリフレクションを通じてサブスクリプション メソッドの登録と実行を実現していることがわかります。サブスクリプション メソッドのスレッド スケジュール、優先度、およびスティッキー イベントは、アノテーションを通じて設定できます。確かに使い方は比較的簡単で、コードも比較的簡潔に見えます。それ自体内でスレッド スケジューリングを実装しており、ユーザーはサブスクリプション メソッドのスレッド スケジューリング モードを設定するだけで済みます。
考える
考え方: リフレクション機構はプログラムのパフォーマンスに影響を与えるため、サブスクライバ オブジェクト クラスがその親クラスのメソッドを含む多くのメソッドを持つクラスである場合、影響があるはずです。前の例と同様に、MainActivity は継承された FragmentActivity であり、FragmentActivity は他のクラスを継承します。これらはすべて走査する必要がありますか?
サブスクリプション オブジェクトを見つける方法を見てみましょう。
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);
}
このループは、findState.clazz が null の場合に停止します。現在の MainActivity のクラスをクエリした後、findState.moveToSuperclass() に移動して、親クラスを見つける方法を確認します。
void moveToSuperclass() {
if (skipSuperClasses) {
clazz = null;
} else {
clazz = clazz.getSuperclass();
String clazzName = clazz.getName();
// Skip system classes, this degrades performance.
// Also we might avoid some ClassNotFoundException (see FAQ for background).
if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") ||
clazzName.startsWith("android.") || clazzName.startsWith("androidx.")) {
clazz = null;
}
}
}
親クラスが「java.」「javax.」「android.」「androidx.」で始まるパッケージ名であれば、システムが提供するクラスが見つかったとみなします。通常、この種のパッケージの先頭にあるクラスは、例の親クラス FragmentActivity のように自分で定義することはありません。このクラスは androidx.fragment.app.FragmentActivity に完全に登録されているため、clazz は null に設定されます。まさにループから抜け出したところだ。
さて、これでこの記事は終わりです。