EventBus后續(xù)深入知識(shí)點(diǎn)整理

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

FindState的妙用

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

  • 通過findUsingInfo(Class<?> subscriberClass)在apt中進(jìn)行查找獲取
  • 使用'findUsingReflection(Class<?> subscriberClass)'方法殿怜,進(jìn)行反射來獲取
    而在這里典蝌,EventBus采用了一個(gè)中間器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é)果subscriberMethods骏掀、subscriberInfo等,另外柱告,還有一個(gè)判斷的標(biāo)志量skipSuperClasses截驮,用來標(biāo)記是否需要進(jìn)行父類的查看查找。

同時(shí)际度,我們可以看出在使用EventBus定義訂閱方法的時(shí)候葵袭,有些通用的邏輯,是可以抽象放置在父類中的乖菱。

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

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

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

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

void moveToSuperclass() {
    ...
}

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

緩存的使用

使用java的绵疲,應(yīng)該要知道頻繁地創(chuàng)建對象哲鸳,是非常消耗資源的,在jvm垃圾回收時(shí)候盔憨,會(huì)出現(xiàn)內(nèi)存抖動(dòng)的問題徙菠。所以,我們在這里郁岩,一定要注意緩存的使用婿奔。

上文中提到的中間器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關(guān)鍵字,來將緩存中FindState的獲取如叼,變?yōu)橥綁K冰木。
而在subscriberMethod的獲取的同時(shí),則對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的獲取,也做了緩存的操作社证,這樣進(jìn)行SubscriberMethod查找的時(shí)候逼龟,則優(yōu)先進(jìn)行緩存的查找:

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

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

反射類方法的使用

反射雖然是比較浪費(fèi)性能的,但對我們Java開發(fā)者來說宜肉,這又是必須掌握的一個(gè)技能匀钧,現(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)容,進(jìn)行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來進(jìn)行方法信息的獲却镆翰蠢;使用Method類的getParameterTypes獲取方法的參數(shù)及getAnnotation獲取方法的注解類。

線程處理類信息的使用

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

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

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

這里垛孔,我們主要看兩個(gè)Poster, BackgroundPosterAsyncPoster

BackgroundPoster - 后臺(tái)任務(wù)執(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方法周荐,將當(dāng)前的訂閱者添加至隊(duì)列PendingPostQueue中,是否立即執(zhí)行僵娃,則需要判斷當(dāng)前隊(duì)列是否還有正在執(zhí)行的任務(wù)概作,若沒有的話,則立即執(zhí)行默怨,若還有執(zhí)行任務(wù)的話讯榕,則只進(jìn)行隊(duì)列的添加。這樣匙睹,保證了后臺(tái)任務(wù)永遠(yuǎn)只會(huì)在一個(gè)線程執(zhí)行愚屁。

AsyncPoster - 異步任務(wù)執(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í)行隊(duì)列 PendingPostQueue

EventBus對事件的執(zhí)行谆棺,采用隊(duì)列的數(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釋放時(shí)改淑,將其添加到緩存池中
    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給我們提供了相當(dāng)強(qiáng)大的功能蔼啦,同時(shí)它的寫法也相當(dāng)有味道,值得我們深深地去研究仰猖∧笾總的來說,其中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)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子一睁,更是在濱河造成了極大的恐慌钻弄,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,548評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件者吁,死亡現(xiàn)場離奇詭異窘俺,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)砚偶,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,497評論 3 399
  • 文/潘曉璐 我一進(jìn)店門批销,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人染坯,你說我怎么就攤上這事均芽。” “怎么了单鹿?”我有些...
    開封第一講書人閱讀 167,990評論 0 360
  • 文/不壞的土叔 我叫張陵掀宋,是天一觀的道長。 經(jīng)常有香客問我仲锄,道長劲妙,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,618評論 1 296
  • 正文 為了忘掉前任儒喊,我火速辦了婚禮镣奋,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘怀愧。我一直安慰自己侨颈,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,618評論 6 397
  • 文/花漫 我一把揭開白布芯义。 她就那樣靜靜地躺著哈垢,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扛拨。 梳的紋絲不亂的頭發(fā)上耘分,一...
    開封第一講書人閱讀 52,246評論 1 308
  • 那天,我揣著相機(jī)與錄音绑警,去河邊找鬼求泰。 笑死,一個(gè)胖子當(dāng)著我的面吹牛计盒,可吹牛的內(nèi)容都是我干的拜秧。 我是一名探鬼主播,決...
    沈念sama閱讀 40,819評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼章郁,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起暖庄,我...
    開封第一講書人閱讀 39,725評論 0 276
  • 序言:老撾萬榮一對情侶失蹤聊替,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后培廓,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惹悄,經(jīng)...
    沈念sama閱讀 46,268評論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,356評論 3 340
  • 正文 我和宋清朗相戀三年肩钠,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泣港。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,488評論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡价匠,死狀恐怖当纱,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情踩窖,我是刑警寧澤坡氯,帶...
    沈念sama閱讀 36,181評論 5 350
  • 正文 年R本政府宣布,位于F島的核電站洋腮,受9級(jí)特大地震影響箫柳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜啥供,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,862評論 3 333
  • 文/蒙蒙 一悯恍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧伙狐,春花似錦涮毫、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,331評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至豫尽,卻和暖如春篙梢,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背美旧。 一陣腳步聲響...
    開封第一講書人閱讀 33,445評論 1 272
  • 我被黑心中介騙來泰國打工渤滞, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人榴嗅。 一個(gè)月前我還...
    沈念sama閱讀 48,897評論 3 376
  • 正文 我出身青樓妄呕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親嗽测。 傳聞我的和親對象是個(gè)殘疾皇子绪励,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,500評論 2 359

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

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