手寫EventBus框架——源碼分析1

哎呀呀

最近感覺框架上非常弱雞蝌衔,于是找到了EventBus,想通過解析源碼蝌蹂,學習架構設計最后并手寫實現(xiàn)EventBus框架胚委。
 那么,gogogo 先從源碼查看出發(fā)叉信,一步一步實現(xiàn)自己的EventBus吧亩冬;

路漫漫其修遠兮

01. 手寫EventBus框架——源碼分析1
02. 手寫EventBus框架——源碼分析2
03. 手寫EventBus框架——動手_整體架構設計
04. 手寫EventBus框架——動手_終結

1. 訂閱、取消訂閱

EventBus3.0 使用方式如下:

public class SampleComponent extends Fragment  
{  
  
    @Override  
    public void onCreate(Bundle savedInstanceState)  
    {  
        super.onCreate(savedInstanceState);  
        EventBus.getDefault().register(this);  
    }  
  
    @Subscribe
    public void gogogo(param)  
    {  
    }
      
    @Override  
    public void onDestroy()  
    {  
        super.onDestroy();  
        EventBus.getDefault().unregister(this);  
    }  
      
}  

1.1 注冊

1.1.1 注冊源碼分析

 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();
        //獲取該類所有訂閱的方法
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
            //訂閱方法
                subscribe(subscriber, subscriberMethod);
            }
        }
    }
 // 緩存訂閱方法
   private void subscribe(Object subscriber, SubscriberMethod subscriberMethod){
   //緩存數(shù)據(jù) 至  subscriptionsByEventType硼身;
   //緩存數(shù)據(jù) 至  typesBySubscriber硅急;
   //緩存數(shù)據(jù) 至  stickyEvents;
   } 

以上就是 register 所做的事情;

  • subscriberMethodFinder.findSubscriberMethods(subscriberClass) 找到所有 EventBus 匹配的方法佳遂;
  • 循環(huán)逐個調用緩存訂閱方法 subscribe营袜,最終緩存進入 subscriptionsByEventTypetypesBySubscriber

看一下如何找到匹配方法列表的丑罪;

***  SubscriberMethodFinder.class ***
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
    
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
        if (subscriberMethods != null) {
            return subscriberMethods;
        }

        if (ignoreGeneratedIndex) {
        //使用反射查找荚板。
            subscriberMethods = findUsingReflection(subscriberClass);
        } else {
        // 使用Info取出方法  默認該方法
            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;
        }
    }    

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

以上可以看出 本質都是調用 findUsingReflection

***  SubscriberMethodFinder.class ***

   private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        //這里的  clazz  ==  subscriberClass;
        while (findState.clazz != null) {
            //找到只有一個參數(shù)的 方法集合;
            findUsingReflectionInSingleClass(findState);
            //轉移到 父 Clazz
            findState.moveToSuperclass();
        }
        //這里 就是返回 findState.subscriberMethods;
        return getMethodsAndRelease(findState);
    }
   private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // 找到所有方法
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // 找到 public方法,包含繼承和接口的方法
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            //判斷修飾符, Public  不包含 static, abstaact
            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];
                        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)) {
                //拋異常
            }
        }
    }

這一段可以看出它的實現(xiàn)機制:核心原理是反射吩屹。

亮點:其中判斷標識符的方式 與Android源碼非常類似跪另;

  int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
  //判斷修飾符, Public  不包含 static, abstaact
  if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0)
    {
      //...
    }

1.1.2 小結——注冊事件分析

注冊的源碼已經分析過了煤搜,來個小小的總結

流程:

  • 先找到所有 方法數(shù)量為1個且?guī)в?code>@Subscribe注解的方法
  • 將方法寄存進我們的緩存當中(subscriptionsByEventTypetypesBySubscriberstickyEvents

其它收獲

  • CopyOnWriteArrayList 線程讀寫安全的列表
  • ConcurrentHashMap支持檢索的完全并發(fā)性的哈希表
  • 存儲的一個設計免绿,非常巧妙。

1.2 取消注冊

比較簡單擦盾。緩存清除

    /** 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 {
            Log.w(TAG, "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--;
                }
            }
        }
    }

2. 發(fā)布

我們平常調用時這樣的

EventBus.getDefault().post(param);
//或者 
EventBus.getDefault().postSticky(param);

o.O

    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }
    
    public void post(Object event) {
       //...
    }

本質都是調用 post 方法;

2.2 post (Object event)

讓我們來瞧瞧 它是什么鬼

    private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };
    
    public void post(Object event) {
        //獲取當前線程 的 PostingThreadState
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        //添加到消息列表
        eventQueue.add(event);

        if (!postingState.isPosting) {
            //判斷是否是主線程
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            //狀態(tài)改為true
            postingState.isPosting = true;
           //...
            try {
                while (!eventQueue.isEmpty()) {
                    //發(fā)送
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                //... 還原狀態(tài)
            }
        }
    }

這邊可以看到 先會從 currentPostingThreadState 取出狀態(tài)嘲驾,各種判斷 然后調用postSingleEvent淌哟。
postSingleEvent 這可是塊硬骨頭;
再難也要啃下它辽故,哼哼...

2.3 postSingleEvent()

    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        if (eventInheritance) {
        // 
           //...
                // 進入 
                subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        //...
        } else {
            subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
        }
        if (!subscriptionFound) {
            //...
            //如果沒有執(zhí)行徒仓,第一次會進入以下方法
            if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                    eventClass != SubscriberExceptionEvent.class) {
                    // 包裝成 NoSubscriberEvent 方法
                post(new NoSubscriberEvent(this, event));
            }
        }
    }
    
    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
            //獲取  subscriptions
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
            for (Subscription subscription : subscriptions) {
               //...
                try {
                    postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                   //...
                }
            }
            return true;
        }
        return false;
    }
    

最后調用方法 在不同的線程內。


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

2.3.1 小結——Post事件分析

** 流程**

  1. 根據(jù)EventType 得到subscriptions = subscriptionsByEventType 中的CopyOnWriteArrayList<Subscription>;
  2. 循環(huán) subscriptions誊垢,并調用postToSubscription發(fā)送 ;
  3. postToSubscription 內根據(jù)線程類型來執(zhí)行方法掉弛;
  4. 發(fā)布了以后 緩存方法直接執(zhí)行。

使用注意點:
從這邊的分析可以看出來彤枢,post調用以后,會直接執(zhí)行訂閱方法筒饰,那么訂閱方(Activity)不在棧頂?shù)臅r候缴啡,如果做些動畫展示,那是很耗性能的瓷们,那就尷尬了业栅。。

這邊梳理了整個事件的流程與原理谬晕,順帶也收獲到了一些干貨
細節(jié)方面沒有那么深入碘裕。

下一篇: 02. 手寫EventBus框架——源碼分析2


希望我的文章不會誤導在觀看的你,如果有異議的地方歡迎討論和指正攒钳。
如果能給觀看的你帶來收獲帮孔,那就是最好不過了。

人生得意須盡歡, 桃花塢里桃花庵
點個關注唄不撑,對文兢,不信你點試試?
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末焕檬,一起剝皮案震驚了整個濱河市姆坚,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌实愚,老刑警劉巖兼呵,帶你破解...
    沈念sama閱讀 216,470評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異腊敲,居然都是意外死亡击喂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,393評論 3 392
  • 文/潘曉璐 我一進店門碰辅,熙熙樓的掌柜王于貴愁眉苦臉地迎上來茫负,“玉大人,你說我怎么就攤上這事乎赴∪谭ǎ” “怎么了潮尝?”我有些...
    開封第一講書人閱讀 162,577評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長饿序。 經常有香客問我勉失,道長,這世上最難降的妖魔是什么原探? 我笑而不...
    開封第一講書人閱讀 58,176評論 1 292
  • 正文 為了忘掉前任乱凿,我火速辦了婚禮,結果婚禮上咽弦,老公的妹妹穿的比我還像新娘徒蟆。我一直安慰自己,他們只是感情好型型,可當我...
    茶點故事閱讀 67,189評論 6 388
  • 文/花漫 我一把揭開白布段审。 她就那樣靜靜地躺著,像睡著了一般闹蒜。 火紅的嫁衣襯著肌膚如雪寺枉。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,155評論 1 299
  • 那天绷落,我揣著相機與錄音姥闪,去河邊找鬼。 笑死砌烁,一個胖子當著我的面吹牛筐喳,可吹牛的內容都是我干的。 我是一名探鬼主播函喉,決...
    沈念sama閱讀 40,041評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼疏唾,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了函似?” 一聲冷哼從身側響起槐脏,我...
    開封第一講書人閱讀 38,903評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撇寞,沒想到半個月后顿天,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 45,319評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蔑担,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,539評論 2 332
  • 正文 我和宋清朗相戀三年牌废,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片啤握。...
    茶點故事閱讀 39,703評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡鸟缕,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情懂从,我是刑警寧澤授段,帶...
    沈念sama閱讀 35,417評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站番甩,受9級特大地震影響侵贵,放射性物質發(fā)生泄漏。R本人自食惡果不足惜缘薛,卻給世界環(huán)境...
    茶點故事閱讀 41,013評論 3 325
  • 文/蒙蒙 一窍育、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧宴胧,春花似錦漱抓、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,664評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至檐迟,卻和暖如春补胚,著一層夾襖步出監(jiān)牢的瞬間码耐,已是汗流浹背追迟。 一陣腳步聲響...
    開封第一講書人閱讀 32,818評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留骚腥,地道東北人敦间。 一個月前我還...
    沈念sama閱讀 47,711評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像束铭,于是被迫代替她去往敵國和親廓块。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,601評論 2 353

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,072評論 25 707
  • 我每周會寫一篇源代碼分析的文章,以后也可能會有其他主題.如果你喜歡我寫的文章的話,歡迎關注我的新浪微博@達達達達s...
    SkyKai閱讀 24,926評論 23 184
  • 路漫漫其修遠兮 01. 手寫EventBus框架——源碼分析102. 手寫EventBus框架——源碼分析203....
    wenld_閱讀 684評論 0 0
  • 近日拴清,《中國詩詞大會》宛如颶風一般襲來,“滿屏競傳飛花令会通,一眾爭說武亦姝”口予。 來自各行各業(yè)、年長年少的選手以詩為劍...
    靖萱z閱讀 721評論 2 5
  • 第一次聽到這篇短文是在一個雨后的清晨涕侈,或許記錯了沪停,但我更愿意相信那是雨后的清晨,我和孩子正在做游戲,歐陽夏丹婉轉柔...
    時汝佳閱讀 3,143評論 0 0