EventBus后續深入知識點整理
根據上一篇文章 淺析EventBus 3.0實現思想 對EventBus的概括,本文針對其中一些重要且比較有意思的知識點,做一下如下的匯總整理 :
FindState的妙用
在EventBus中,會根據class信息,來獲取 SubscriberMethod ,這里會在 SubscriberMethodFinder 中進行處理,提供了兩種方式來進行獲取:
- 通過 findUsingInfo(Class<?> subscriberClass) 在apt中進行查找獲取
- 使用’findUsingReflection(Class<?> subscriberClass)‘方法,進行反射來獲取 而在這里,EventBus采用了一個中間器 FindState ,來看一下它的結構:
static class FindState {
final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
Class<?> subscriberClass;
Class<?> clazz;
boolean skipSuperClasses;
SubscriberInfo subscriberInfo;
}
這里對查找的狀態值做了一些封裝,其中有訂閱類 subscriberClass ,事件對象 clazz ,以及查找的結果 subscriberMethods 、 subscriberInfo 等,另外,還有一個判斷的標志量 skipSuperClasses ,用來標記是否需要進行父類的查看查找。
同時,我們可以看出在使用EventBus定義訂閱方法的時候,有些通用的邏輯,是可以抽象放置在父類中的。
為什么要使用FindState呢?首先是面向對象封裝的采用,那么看看它給我們提供了哪些方法?
void initForSubscriber(Class<?> subscriberClass) {
...
}
boolean checkAdd(Method method, Class<?> eventType) {
...
}
private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
...
}
void moveToSuperclass() {
...
}
方法中的 initForSubscriber 是用來初始化傳入訂閱類的,兩個check方法則是用來檢查方法信息的,這樣用來保證獲取的訂閱方法都是合法的。 moveToSuperClass 則是需要查看父類中的訂閱方法。這樣對方法檢查的邏輯,我們就把它們抽象在了FindState中。
緩存的使用
使用java的,應該要知道頻繁地創建對象,是非常消耗資源的,在jvm垃圾回收時候,會出現內存抖動的問題。所以,我們在這里,一定要注意緩存的使用。
上文中提到的中間器FindState,就采用了緩存:
private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
指定了FindState的緩存大小為4,并使用一維的靜態數組,所以這里需要注意線程同步的問題:
private FindState prepareFindState() {
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
FindState state = FIND_STATE_POOL[i];
if (state != null) {
FIND_STATE_POOL[i] = null;
return state;
}
}
}
return new FindState();
}
這段是用來獲取FindState, 可以看到的是對這段緩存的獲取使用了 synchronized 關鍵字,來將緩存中FindState的獲取,變為同步塊。 而在subscriberMethod的獲取的同時,則對FindState的緩存做了添加的操作,同樣是也必須是同步代碼塊:
private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {
List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);
findState.recycle();
synchronized (FIND_STATE_POOL) {
for (int i = 0; i < POOL_SIZE; i++) {
if (FIND_STATE_POOL[i] == null) {
FIND_STATE_POOL[i] = findState;
break;
}
}
}
return subscriberMethods;
}
另外,EventBus也對subsciberMethod的獲取,也做了緩存的操作,這樣進行SubscriberMethod查找的時候,則優先進行緩存的查找:
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
這里,使用的是數據結構是 ConcurrentHashMap ,就可以不必寫大量的同步代碼塊了。
反射類方法的使用
反射雖然是比較浪費性能的,但對我們Java開發者來說,這又是必須掌握的一個技能,現在來熟悉一下EventBus中通過 @Subscribe 注解對 SubscriberMethod 的查找:
private void findUsingReflectionInSingleClass(FindState findState) {
Method[] methods;
// 優先使用getDeclareMethods方法,如注釋中所說,比getMethods方法塊。
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
methods = findState.clazz.getMethods();
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();
// 方法的參數(事件類型)長度只能為1
if (parameterTypes.length == 1) {
Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
if (subscribeAnnotation != null) {
Class<?> eventType = parameterTypes[0];
// 獲取到annotation中的內容,進行subscriberMethod的添加
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)) {
//拋出方法參數只能為1的異常
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)) {
//拋出方法訪問符只能為public的異常
String methodName = method.getDeclaringClass().getName() + "." + method.getName();
throw new EventBusException(methodName +
" is a illegal @Subscribe method: must be public, non-static, and non-abstract");
}
}
}
其中,最核心的類便是 Method 和 Class ,通過 Class 的 getDeclaredMethods 及 getMethods 來進行方法信息的獲取;使用 Method 類的 getParameterTypes 獲取方法的參數及 getAnnotation 獲取方法的注解類。
線程處理類信息的使用
在EventBus類中,定義了4種線程處理的策略:
public enum ThreadMode { POSTING, MAIN, BACKGROUND, ASYNC }
POSTING 采用與事件發布者相同的線程, MAIN 指定為主線程, BACKGROUND 指定為后臺線程,而 ASYNC 相比前三者不同的地方是可以處理耗時的操作,其采用了線程池,且是一個異步執行的過程,即事件的訂閱者可以立即得到執行。
這里,我們主要看兩個Poster, BackgroundPoster 和 AsyncPoster :
BackgroundPoster – 后臺任務執行
final class BackgroundPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
private volatile boolean executorRunning;
BackgroundPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
synchronized (this) {
queue.enqueue(pendingPost);
if (!executorRunning) {
executorRunning = true;
eventBus.getExecutorService().execute(this);
}
}
}
@Override
public void run() {
try {
try {
while (true) {
PendingPost pendingPost = queue.poll(1000);
if (pendingPost == null) {
synchronized (this) {
// Check again, this time in synchronized
pendingPost = queue.poll();
if (pendingPost == null) {
executorRunning = false;
return;
}
}
}
eventBus.invokeSubscriber(pendingPost);
}
} catch (InterruptedException e) {
Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);
}
} finally {
executorRunning = false;
}
}
}
代碼中,主要通過 enqueue 方法,將當前的訂閱者添加至隊列 PendingPostQueue 中,是否立即執行,則需要判斷當前隊列是否還有正在執行的任務,若沒有的話,則立即執行,若還有執行任務的話,則只進行隊列的添加。這樣,保證了后臺任務永遠只會在一個線程執行。
AsyncPoster – 異步任務執行
class AsyncPoster implements Runnable {
private final PendingPostQueue queue;
private final EventBus eventBus;
AsyncPoster(EventBus eventBus) {
this.eventBus = eventBus;
queue = new PendingPostQueue();
}
public void enqueue(Subscription subscription, Object event) {
PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
queue.enqueue(pendingPost);
eventBus.getExecutorService().execute(this);
}
@Override
public void run() {
PendingPost pendingPost = queue.poll();
if(pendingPost == null) {
throw new IllegalStateException("No pending post available");
}
eventBus.invokeSubscriber(pendingPost);
}
}
這段代碼就很簡單了,直接通過線程池調用執行,相比 BackgroundPoster 執行來說,則沒有等待的過程。
事件執行隊列 PendingPostQueue
EventBus對事件的執行,采用隊列的數據結構:
final class PendingPostQueue {
private PendingPost head;
private PendingPost tail;
synchronized void enqueue(PendingPost pendingPost) {
if (pendingPost == null) {
throw new NullPointerException("null cannot be enqueued");
}
if (tail != null) {
tail.next = pendingPost;
tail = pendingPost;
} else if (head == null) {
head = tail = pendingPost;
} else {
throw new IllegalStateException("Head present, but no tail");
}
notifyAll();
}
synchronized PendingPost poll() {
PendingPost pendingPost = head;
if (head != null) {
head = head.next;
if (head == null) {
tail = null;
}
}
return pendingPost;
}
synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
if (head == null) {
wait(maxMillisToWait);
}
return poll();
}
}
而對 PendingPost 的封裝,使用了數據緩存池:
final class PendingPost {
private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();
Object event;
Subscription subscription;
PendingPost next;
// 對PendingPost的獲取,優先從緩存池中拿
private PendingPost(Object event, Subscription subscription) {
this.event = event;
this.subscription = subscription;
}
static PendingPost obtainPendingPost(Subscription subscription, Object event) {
synchronized (pendingPostPool) {
int size = pendingPostPool.size();
if (size > 0) {
PendingPost pendingPost = pendingPostPool.remove(size - 1);
pendingPost.event = event;
pendingPost.subscription = subscription;
pendingPost.next = null;
return pendingPost;
}
}
return new PendingPost(event, subscription);
}
// 對PendingPost釋放時,將其添加到緩存池中
static void releasePendingPost(PendingPost pendingPost) {
pendingPost.event = null;
pendingPost.subscription = null;
pendingPost.next = null;
synchronized (pendingPostPool) {
// Don't let the pool grow indefinitely
if (pendingPostPool.size() < 10000) {
pendingPostPool.add(pendingPost);
}
}
}
}
可以看到其對緩存的大小限制到10000,好任性啊。。
總結
EventBus給我們提供了相當強大的功能,同時它的寫法也相當有味道,值得我們深深地去研究。總的來說,其中EventBus采用了Facade模式,方便開發者的統一調用;另外不同的線程策略,以及反射代碼,Apt處理代碼生成以及緩存的大量使用。
來自: http://alighters.com/blog/2016/05/24/eventbus-3-dot-0-indepth-knowledge/