EventBus之高效使用
說起 EventBus,作爲一名 Android 開發者,應該不會太陌生,但是我們大部分都會根據官方文檔直接進行使用,其實還有一種比較高效的使用方式。就是不使用註解的方式,在編譯時期,對相關注冊方法進行註冊。
這其實就相當於用空間換時間的一種常規操作了。這裏附上 官方源碼 和 官方文檔 的地址。
先來貼一張官方文檔中的圖解,讓大家對 EventBus 的工作機制現有一個宏觀上的回憶。
常規使用
先來看看常規的使用方式。
1、接入EventBus
implementation 'org.greenrobot:eventbus:3.2.0'
2、使用
//1、創建訂閱事件
class DataModel(val msg: String)
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//2、註冊訂閱
EventBus.getDefault().register(this)
}
//3、創建接收發布的事件的方法
@Subscribe(threadMode = org.greenrobot.eventbus.ThreadMode.MAIN, sticky = true, priority = 2)
fun dataReceive(dataModel: DataModel) {
Toast.makeText(this, dataModel.msg, Toast.LENGTH_LONG).show()
}
fun post(view: View) {
//4、發佈事件
EventBus.getDefault().post(DataModel("send success"))
}
override fun onDestroy() {
//5、取消訂閱
EventBus.getDefault().unregister(this)
super.onDestroy()
}
}
使用方式很簡單,基本上和官方介紹的一樣,爲了方便就在這裏聚合了一下。
下面來分析這種方式,是怎麼實現訂閱者與發佈者之間進行信息交互的。
常規使用之源碼分析
註冊訂閱者
在分析具體源碼之前,先來看看幾個相關的輔助類
1、Subscribe 作用於訂閱事件方法的註解類
//運行時
@Retention(RetentionPolicy.RUNTIME)
//目標作用於方法
@Target({ElementType.METHOD})
public @interface Subscribe {
//線程模式,有當線程、主線程、子線程、異步線程等。
ThreadMode threadMode() default ThreadMode.POSTING;
//是否爲粘性事件,就是可以先進行發佈,後進行註冊,註冊時候會立刻收到發佈的事件
boolean sticky() default false;
//接受的優先級,數值越大優先級越高
int priority() default 0;
}
2、Subscription、SubscriberMethod 訂閱方法的封裝類
final class Subscription {
//訂閱的對象,例如上面的 MainActivity 對象(這裏需要注意,不是類,而是具體的對象)
final Object subscriber;
//註冊類中的訂閱方法(與對象無關,解析相關類中的訂閱方法)
final SubscriberMethod subscriberMethod;
}
public class SubscriberMethod {
//訂閱的方法名
final Method method;
//執行的線程環境
final ThreadMode threadMode;
//訂閱事件類型(訂閱方法中的參數類型)
final Class<?> eventType;
//優先級
final int priority;
//是否爲粘性事件
final boolean sticky;
//用於比較兩個方法是否相同,具體在繼承中會用到
String methodString;
}
基於上面的使用方法,我們可以直接從第二步的註冊訂閱事件的 register()
方法來進行源碼的跟蹤。
先來看看註冊事件
public class EventBus {
//訂閱方法的集合:<訂閱類型,該類型的多有方法集合>,方便用於事件分發
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//訂閱方法的集合:<訂閱者對象,該類中的所有註冊方法>,方便用於去掉訂閱
private final Map<Object, List<Class<?>>> typesBySubscriber;
//所有的粘性事件集合:<訂閱事件類型,事件對象>,用於後續新事件註冊後的粘性事件分發
private final Map<Class<?>, Object> stickyEvents;
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
//從註冊類中找出所有訂閱事件的相關方法
//下文在分析 SubscriberMethodFinder 這個輔助類,這裏先跳過,看註冊的主流程
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//拿到當前訂閱事件type的訂閱集合
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;
}
}
//拿到訂閱對象的集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//將訂閱事件添加到訂閱對象的集合中
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
......
//如果是粘性事件,則判斷是否需要進行派發
//這也就是爲什麼可以先發布事件,後面進行註冊也可以收到發佈事件的原因了
//每次新註冊方法時都會判斷是否需要進行粘性事件的分發
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
......
}
}
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
if (stickyEvent != null) {
postToSubscription(newSubscription, stickyEvent, isMainThread());
}
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
......
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);
}
}
}
下面來看下上文中略過的 SubscriberMethodFinder
輔助類。
這個類的作用就是從傳入的註冊類中找出訂閱方法的集合並返回。
class SubscriberMethodFinder {
private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
//從緩存中查找是否曾經註冊過,如果有則直接返回
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
//這裏就是上文中註冊方法調用的方法,根據訂閱的 class 尋找該類中的所有訂閱方法
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()) {
//如果註冊的類中沒有訂閱方法,則直接拋出異常。這就有點坑,容易出bug
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;
}
}
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
//循環遍歷當前類和父類,通過反射查找相應的訂閱方法
while (findState.clazz != null) {
//查找當前類的訂閱方法
findUsingReflectionInSingleClass(findState);
//查找完畢後,將當前 class 定位到父類
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
try {
// 拿到該類中的所有方法,不包括父類的方法
methods = findState.clazz.getDeclaredMethods();
} catch (Throwable th) {
try {
// 拿到該類中的所有方法,包括父類的方法
methods = findState.clazz.getMethods();
} catch (LinkageError error) {
......
}
findState.skipSuperClasses = true;
}
// 遍歷所有方法,找到符合目標的訂閱方法
for (Method method : methods) {
int modifiers = method.getModifiers();
// 校驗方法的權限,只能是 public 的
if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
Class<?>[] parameterTypes = method.getParameterTypes();
// 並且該方法的參數只能爲一個
if (parameterTypes.length == 1) {
// 拿到該方法的 Subscribe 註解屬性,如果沒有則不是訂閱方法
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)) {
......
}
} else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
......
}
}
}
}
到這裏,註冊的流程基本上就完了。上面的註釋寫的基本上很詳細了,也是從上到下的一個流程。
發佈訂閱事件
註冊完成後,就是接受訂閱事件了,以發佈事件爲入口,看看事件發佈後,訂閱的方法是如何接受到發佈的訂閱事件。
public class EventBus {
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;
}
}
}
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
......
postSingleEventForEventType(event, postingState, eventClass);
......
}
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) {
......
// 遍歷所有訂閱該事件的方法,並進行分發
postToSubscription(subscription, event, postingState.isMainThread);
......
}
return true;
}
return false;
}
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
......
// 這裏會進行相應的線程切換,就不做過多解析
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);
}
}
}
發佈事件到這就結束了,就是從訂閱的集合中拿到訂閱的相關方法並進行調用,就完事了。
解註冊訂閱者
最後就是解註冊了,相比分發就更爲簡單了,沒什麼說的,就是從集合中找出,刪除完事。
public class EventBus {
public synchronized void unregister(Object subscriber) {
// 拿到解註冊類中訂閱的所有方法
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
// 遍歷所有方法,根據事件類型,從 subscriptionsByEventType 集合中移除相應的訂閱事件
unsubscribeByEventType(subscriber, eventType);
}
// 移除
typesBySubscriber.remove(subscriber);
} else {
logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
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--;
}
}
}
}
}
以上就是我們正常使用 EventBus 的全過程解析了,有沒有豁然開朗。其實原理並沒有多麼深奧,只要靜下心看看就可以理解。更多需要注意的就是那些異常的處理以及細節的考慮了,這些纔是這個項目的精華所在。
高效使用
既然題目是高效使用,那這部分纔是要說的重點了,上面哪些相信大家都已經耳熟能詳了,主要是總結一下。
在介紹使用之前,先看看一組數據對比,根據官方作者自述,這種方式可以進一步提升 app 的運行效率。因爲在其編譯時期就爲其建立了一份索引庫,而不是在運行時纔去建立。相當於一種空間換時間,來提高 app 的運行效率
下面來看看怎麼使用
1、在項目根目錄中的 build
文件中新增 android-apt
插件引用
dependencies {
classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8'
}
2、在項目的 build
文件中使用插件
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg('eventBusIndex', 'com.silence.eventbusdemo.EventBusIndex')
}
}
dependencies {
implementation 'org.greenrobot:eventbus:3.2.0'
kapt 'org.greenrobot:eventbus-annotation-processor:3.0.1'
}
配置完成後點擊 Build -> Rebuild Project 。重新 build 項目後,會在 build/generated/source/kapt/debug/com/silence/eventbusdemo
目錄下生成一個 EventBusIndex
類。
這個類比較簡單。就是在編譯期間找到所有的訂閱方法,然後構造相關訂閱方法的信息,保存起來。在進行事件發佈時,直接從這裏面找就可以了。
public class EventBusIndex 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("dataReceive", DataModel.class, ThreadMode.MAIN, 2, true),
}));
}
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;
}
}
}
3、在 Application 中引用生成的類
//別忘了在 manifest 文件中聲明 aplication
class DemoApplication: Application() {
override fun onCreate() {
super.onCreate()
EventBus.builder().addIndex(EventBusIndex()).installDefaultEventBus()
}
}
4、使用和普通的正常使用一樣,這裏就不贅述了
高效使用之源碼分析
還記得上面跳過的那個獲取訂閱事件方法嗎,有一處沒有分析,這裏就分析一下。
class SubscriberMethodFinder {
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
if (ignoreGeneratedIndex) {
//使用反射尋找,由於沒有進行任何相關配置,所以默認會使用反射進行查找,所以效率相對來說比較底下
subscriberMethods = findUsingReflection(subscriberClass);
} else {
//使用添加的信息尋找,相關配置完成後會走到這裏
subscriberMethods = findUsingInfo(subscriberClass);
}
}
private List<SubscriberMethod> findUsingInfo(Class<?> 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);
}
private SubscriberInfo getSubscriberInfo(FindState findState) {
......
for (SubscriberInfoIndex index : subscriberInfoIndexes) {
// 根據訂閱類,從編譯時生成的集合中找出訂閱方法的信息
SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
if (info != null) {
return info;
}
}
......
return null;
}
}
到這,普通版和進化版就介紹完畢了。有興趣的同學可以看看那個插件是怎麼生成相關類的,這裏就不貼了,官網源碼上就有。
手擼簡易版 CustomEventBus
爲了加深印象,這裏 100 多行手擼了一個 簡易版 CustomEventBus。僅供學習,不能再項目中使用昂。
這裏在附上 CustomeEventBus 源碼。