EventBus后續(xù)深入知識點整理

根據(jù)上一篇文章淺析EventBus 3.0實現(xiàn)思想 對EventBus的概括,本文針對其中一些重要且比較有意思的知識點,做一下如下的匯總整理 :

FindState的妙用

在EventBus中,會根據(jù)class信息,來獲取SubscriberMethod,這里會在SubscriberMethodFinder中進行處理,提供了兩種方式來進行獲取:

  • 通過findUsingInfo(Class<?> subscriberClass)在apt中進行查找獲取
  • 使用'findUsingReflection(Class<?> subscriberClass)'方法,進行反射來獲取
    而在這里,EventBus采用了一個中間器FindState,來看一下它的結(jié)構(gòu):
static class FindState {
    final List<SubscriberMethod> subscriberMethods = new ArrayList<>();

    Class<?> subscriberClass;
    Class<?> clazz;
    boolean skipSuperClasses;
    SubscriberInfo subscriberInfo;
}

這里對查找的狀態(tài)值做了一些封裝,其中有訂閱類subscriberClass,事件對象clazz,以及查找的結(jié)果subscriberMethodssubscriberInfo等,另外,還有一個判斷的標志量skipSuperClasses,用來標記是否需要進行父類的查看查找。

同時,我們可以看出在使用EventBus定義訂閱方法的時候,有些通用的邏輯,是可以抽象放置在父類中的。

為什么要使用FindState呢?首先是面向?qū)ο蠓庋b的采用,那么看看它給我們提供了哪些方法?

void initForSubscriber(Class<?> subscriberClass) {
    ...
}

boolean checkAdd(Method method, Class<?> eventType) {
    ...
}

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    ...
}

void moveToSuperclass() {
    ...
}

方法中的initForSubscriber是用來初始化傳入訂閱類的,兩個check方法則是用來檢查方法信息的,這樣用來保證獲取的訂閱方法都是合法的。moveToSuperClass則是需要查看父類中的訂閱方法。這樣對方法檢查的邏輯,我們就把它們抽象在了FindState中。

緩存的使用

使用java的,應該要知道頻繁地創(chuàng)建對象,是非常消耗資源的,在jvm垃圾回收時候,會出現(xiàn)內(nèi)存抖動的問題。所以,我們在這里,一定要注意緩存的使用。

上文中提到的中間器FindState,就采用了緩存:

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

指定了FindState的緩存大小為4,并使用一維的靜態(tài)數(shù)組,所以這里需要注意線程同步的問題:

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的獲取,變?yōu)橥綁K。
而在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查找的時候,則優(yōu)先進行緩存的查找:

private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();

這里,使用的是數(shù)據(jù)結(jié)構(gòu)是ConcurrentHashMap,就可以不必寫大量的同步代碼塊了。

反射類方法的使用

反射雖然是比較浪費性能的,但對我們Java開發(fā)者來說,這又是必須掌握的一個技能,現(xiàn)在來熟悉一下EventBus中通過@Subscribe注解對SubscriberMethod的查找:

private void findUsingReflectionInSingleClass(FindState findState) {
     Method[] methods;
     // 優(yōu)先使用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();
             // 方法的參數(shù)(事件類型)長度只能為1
             if (parameterTypes.length == 1) {
                 Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                 if (subscribeAnnotation != null) {
                     Class<?> eventType = parameterTypes[0];
                     // 獲取到annotation中的內(nèi)容,進行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)) {
                 //拋出方法參數(shù)只能為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");
         }
     }
 }

其中,最核心的類便是MethodClass,通過ClassgetDeclaredMethodsgetMethods來進行方法信息的獲取;使用Method類的getParameterTypes獲取方法的參數(shù)及getAnnotation獲取方法的注解類。

線程處理類信息的使用

在EventBus類中,定義了4種線程處理的策略:

public enum ThreadMode {
    POSTING,
    MAIN,    
    BACKGROUND,
    ASYNC
}

POSTING采用與事件發(fā)布者相同的線程,MAIN指定為主線程,BACKGROUND指定為后臺線程,而ASYNC相比前三者不同的地方是可以處理耗時的操作,其采用了線程池,且是一個異步執(zhí)行的過程,即事件的訂閱者可以立即得到執(zhí)行。

這里,我們主要看兩個Poster, BackgroundPosterAsyncPoster

BackgroundPoster - 后臺任務執(zhí)行

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中,是否立即執(zhí)行,則需要判斷當前隊列是否還有正在執(zhí)行的任務,若沒有的話,則立即執(zhí)行,若還有執(zhí)行任務的話,則只進行隊列的添加。這樣,保證了后臺任務永遠只會在一個線程執(zhí)行。

AsyncPoster - 異步任務執(zhí)行

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);
    }

}

這段代碼就很簡單了,直接通過線程池調(diào)用執(zhí)行,相比BackgroundPoster執(zhí)行來說,則沒有等待的過程。

事件執(zhí)行隊列 PendingPostQueue

EventBus對事件的執(zhí)行,采用隊列的數(shù)據(jù)結(jié)構(gòu):

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的封裝,使用了數(shù)據(jù)緩存池:

final class PendingPost {
    private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

    Object event;
    Subscription subscription;
    PendingPost next;

     // 對PendingPost的獲取,優(yōu)先從緩存池中拿
    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,好任性啊。。

總結(jié)

EventBus給我們提供了相當強大的功能,同時它的寫法也相當有味道,值得我們深深地去研究。總的來說,其中EventBus采用了Facade模式,方便開發(fā)者的統(tǒng)一調(diào)用;另外不同的線程策略,以及反射代碼,Apt處理代碼生成以及緩存的大量使用。

轉(zhuǎn)載請注明原文鏈接:http://alighters.com/blog/2016/05/24/eventbus-3-dot-0-indepth-knowledge/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,598評論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評論 19 139
  • 最近在項目中使用了EventBus(3.0),覺得非常好用,于是就看了一些關于EventBus源碼分析的文章,現(xiàn)在...
    shenhuniurou閱讀 1,539評論 0 4
  • 今晚休息前,看著電視,忽然想起今天是15日了,壞了,14日作業(yè)雨還未交作業(yè)。再過半小時不交作業(yè)就徹底是缺失本次作業(yè)...
    李瑞麗閱讀 169評論 0 0
  • Halloween it's an autumn fastival in the UK,We wearing sc...
    5505王妙伊閱讀 287評論 0 1