江南带你看EventBus解说篇

Advertisement

订阅(注册):EventBus的EventBus.getDefault().register(this);就是便利当前类的所有方法,寻找以onEvent开头的放大,以键值队的形式存储。
发布:EventBus.getDefault().post(param);

发布很简单就是调用这个方法,然后EventBus就会在内部存储的方法中扫描,找到参数匹配的就会调用反射去执行,它的内部就是维持一个Map集合,键就是当前类的class类型。然后根据你传入参数的类型进行查找相应的方法,你们觉得还是个事么?
过程就是在通过register注册,Map(当前类的class类型,)

下面我们通过源码来看下EventBus的执行流程。

public static EventBus getDefault() {
    if(defaultInstance == null) {
        Class var0 = EventBus.class;
        synchronized(EventBus.class) {
            if(defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }

    return defaultInstance;
}

通过我们java基础可以知道,这样做的好处
1:为了防止并发多线程的访问我们加了同步,但这样会影响效率,因为每次走这里都得排队等待,比较慢。
2:为了解决上面每次进来都得排队等待的过程,通过两个非空判断,提高了执行效率

接着我们看下register都做了什么?

EventBus.getDefault().register(this);
public void register(Object subscriber) {
    this.register(subscriber, "onEvent", false, 0);
}
private synchronized void register(Object subscriber, String methodName, boolean sticky, int priority) {
    List subscriberMethods = this.subscriberMethodFinder.findSubscriberMethods(subscriber.getClass(), methodName);
    Iterator var7 = subscriberMethods.iterator();

    while(var7.hasNext()) {
        SubscriberMethod subscriberMethod = (SubscriberMethod)var7.next();
        this.subscribe(subscriber, subscriberMethod, sticky, priority);
    }

}

ubscriber 是我们扫描类的对象,也就是我们代码中常见的this;
methodName 这个是写死的:“onEvent”,用于确定扫描什么开头的方法,可见我们的类中都是以这个开头。
sticky 这个参数,解释源码的时候解释,暂时不用管
priority 优先级,优先级越高,在调用的时候会越先调用。
通过findSubscriberMethods可以看出传入一个class类型,以及一个方法的前缀,返回的List,肯定是去遍历该类的所有方法,根据传入的方法前缀去匹配,匹配成功后返回一个封装的list。

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass, String eventMethodName) {
    String key = subscriberClass.getName() + '.' + eventMethodName;
    Map clazz = methodCache;
    List subscriberMethods;
    synchronized(methodCache) {
        subscriberMethods = (List)methodCache.get(key);
    }

    if(subscriberMethods != null) {
        return subscriberMethods;
    } else {
        ArrayList var23 = new ArrayList();
        Class var24 = subscriberClass;
        HashSet eventTypesFound = new HashSet();

        for(StringBuilder methodKeyBuilder = new StringBuilder(); var24 != null; var24 = var24.getSuperclass()) {
            String name = var24.getName();
            if(name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
                break;
            }

            Method[] methods = var24.getMethods();
            Method[] var13 = methods;
            int var12 = methods.length;

            for(int var11 = 0; var11 < var12; ++var11) {
                Method method = var13[var11];
                String methodName = method.getName();
                if(methodName.startsWith(eventMethodName)) {
                    int modifiers = method.getModifiers();
                    if((modifiers & 1) != 0 && (modifiers & 1032) == 0) {
                        Class[] parameterTypes = method.getParameterTypes();
                        if(parameterTypes.length == 1) {
                            String modifierString = methodName.substring(eventMethodName.length());
                            ThreadMode threadMode;
                            if(modifierString.length() == 0) {
                                threadMode = ThreadMode.PostThread;
                            } else if(modifierString.equals("MainThread")) {
                                threadMode = ThreadMode.MainThread;
                            } else if(modifierString.equals("BackgroundThread")) {
                                threadMode = ThreadMode.BackgroundThread;
                            } else {
                                if(!modifierString.equals("Async")) {
                                    if(!skipMethodVerificationForClasses.containsKey(var24)) {
                                        throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                                    }
                                    continue;
                                }

                                threadMode = ThreadMode.Async;
                            }

                            Class eventType = parameterTypes[0];
                            methodKeyBuilder.setLength(0);
                            methodKeyBuilder.append(methodName);
                            methodKeyBuilder.append('>').append(eventType.getName());
                            String methodKey = methodKeyBuilder.toString();
                            if(eventTypesFound.add(methodKey)) {
                                var23.add(new SubscriberMethod(method, threadMode, eventType));
                            }
                        }
                    } else if(!skipMethodVerificationForClasses.containsKey(var24)) {
                        Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + var24 + "." + methodName);
                    }
                }
            }
        }

        if(var23.isEmpty()) {
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called " + eventMethodName);
        } else {
            Map var25 = methodCache;
            synchronized(methodCache) {
                methodCache.put(key, var23);
                return var23;
            }
        }
    }
}

你只要记得一件事:扫描了所有的方法,把匹配的方法最终保存在subscriptionsByEventType(Map,key:eventType ; value:CopyOnWriteArrayList )中;
eventType是我们方法参数的Class,Subscription中则保存着subscriber, subscriberMethod(method, threadMode, eventType), priority;包含了执行改方法所需的一切

register完毕后,知道了EventBus如何存储我们的方法的,下面我们看看post是如何调用我们的方法的。
由于之前我们知道eventbus通过map集合把我们的方法存储到了subscriptionsByEventType中,那么是post中肯定会去subscriptionsByEventType中去取方法然后调用。

public void post(Object event) {
    EventBus.PostingThreadState postingState = (EventBus.PostingThreadState)this.currentPostingThreadState.get();
    List eventQueue = postingState.eventQueue;
    eventQueue.add(event);
    if(!postingState.isPosting) {
        postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
        postingState.isPosting = true;
        if(postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        } else {
            try {
                while(!eventQueue.isEmpty()) {
                    this.postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }

        }
    }
}

由第二三行代码可知道,当调研post的时候就回把当前线程的PostingThreadState存储到eventQueue中
把我们传入的event,保存到了当前线程中的一个变量PostingThreadState的eventQueue中。
10行:判断当前是否是UI线程。
16-18行:遍历队列中的所有的event,调用postSingleEvent(eventQueue.remove(0), postingState)方法。
这里大家会不会有疑问,每次post都会去调用整个队列么,那么不会造成方法多次调用么?
可以看到第7-8行,有个判断,就是防止该问题的,isPosting=true了,就不会往下走了。

下面再看下postSingleEvent()这个参数就是我们传入的实参。
然后根据这个
将我们的event,即post传入的实参;以及postingState传入到postSingleEvent中。
2-3行:根据event的Class,去得到一个List

private void postSingleEvent(Object event, EventBus.PostingThreadState postingState) throws Error {
    Class eventClass = event.getClass();
    List eventTypes = this.findEventTypes(eventClass);
    boolean subscriptionFound = false;
    int countTypes = eventTypes.size();

    for(int h = 0; h < countTypes; ++h) {
        Class clazz = (Class)eventTypes.get(h);
        CopyOnWriteArrayList subscriptions;
        synchronized(this) {
            subscriptions = (CopyOnWriteArrayList)this.subscriptionsByEventType.get(clazz);
        }

        if(subscriptions != null && !subscriptions.isEmpty()) {
            Iterator var11 = subscriptions.iterator();

            while(var11.hasNext()) {
                Subscription subscription = (Subscription)var11.next();
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;

                try {
                    this.postToSubscription(subscription, event, postingState.isMainThread);
                    aborted = postingState.canceled;
                } finally {
                    postingState.event = null;
                    postingState.subscription = null;
                    postingState.canceled = false;
                }

                if(aborted) {
                    break;
                }
            }

            subscriptionFound = true;
        }
    }

    if(!subscriptionFound) {
        Log.d(TAG, "No subscribers registered for event " + eventClass);
        if(eventClass != NoSubscriberEvent.class && eventClass != SubscriberExceptionEvent.class) {
            this.post(new NoSubscriberEvent(this, event));
        }
    }

}

下面看如何执行反射

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch($SWITCH_TABLE$de$greenrobot$event$ThreadMode()[subscription.subscriberMethod.threadMode.ordinal()]) {
    case 1:
        this.invokeSubscriber(subscription, event);
        break;
    case 2:
        if(isMainThread) {
            this.invokeSubscriber(subscription, event);
        } else {
            this.mainThreadPoster.enqueue(subscription, event);
        }
        break;
    case 3:
        if(isMainThread) {
            this.backgroundPoster.enqueue(subscription, event);
        } else {
            this.invokeSubscriber(subscription, event);
        }
        break;
    case 4:
        this.asyncPoster.enqueue(subscription, event);
        break;
    default:
        throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }

}

直接反射调用;也就是说在当前的线程直接调用该方法;
case MainThread:
首先去判断当前如果是UI线程,则直接调用;否则: mainThreadPoster.enqueue(subscription, event);把当前的方法加入到队列,然后直接通过handler去发送一个消息,在handler的handleMessage中,去执行我们的方法。说白了就是通过Handler去发送消息,然后执行的。
case BackgroundThread:
如果当前非UI线程,则直接调用;如果是UI线程,则将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用
executorService = Executors.newCachedThreadPool();。
case Async:将任务加入到后台的一个队列,最终由Eventbus中的一个线程池去调用;线程池与BackgroundThread用的是同一个。
这么说BackgroundThread和Async有什么区别呢?
BackgroundThread中的任务,一个接着一个去调用,中间使用了一个布尔型变量handlerActive进行的控制。
Async则会动态控制并发。

到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~
其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

到此,我们完整的源码分析就结束了,总结一下:register会把当前类中匹配的方法,存入一个map,而post会根据实参去map查找进行反射调用。分析这么久,一句话就说完了~~
其实不用发布者,订阅者,事件,总线这几个词或许更好理解,以后大家问了EventBus,可以说,就是在一个单例内部维持着一个map对象存储了一堆的方法;post无非就是根据参数去查找方法,进行反射调用。

Similar Posts:

  • 相信大家看了这篇文章对Oracle如何工作有一个形象的了解!

    相信大家看了这篇文章对Oracle如何工作有一个形象的了解! 摘自----<Oracle备份与恢复> ----------------------------------------------------------------------------------------------------------------- 一个名叫Sid的男人,狂热地爱好拍摄.保存和整理照片.Sid的妻子名叫Debbie,他们有三个儿子Logan.Archie和Chuck.他有一所很大的房子,同住的有男管

  • 华为boss力荐公司高层看的一篇文章,很长很经典 很值得一看

    今天是 22 岁的最后一天.几个月前,我从沃顿商学院毕业,用文凭上"最高荣誉毕业"的标签安抚了已经年过半百的老妈,然后转头辞去了毕业后的第一份工作,跟一家很受尊敬的公司.还有 150 万的年薪道了别,回到了上海,加入了"刚毕业就失业"俱乐部,开始了一天三顿盒饭的新生活,中间许多精彩剧情暂时略过. 我肯定不是第一个做过这样事的人,也肯定不会是最后一个.所以在说自己的一些有趣故事前,我想借用大家(包括 30 岁甚至 40 岁以上的朋友)的一点时间和一点平和的心态,和大家

  • 转 相信大家看了这篇文章对Oracle如何工作有一个形象的了解!

    相信大家看了这篇文章对Oracle如何工作有一个形象的了解! 摘自----<Oracle备份与恢复> ----------------------------------------------------------------------------------------------------------------- 一个名叫Sid的男人,狂热地爱好拍摄.保存和整理照片.Sid的妻子名叫Debbie,他们有三个儿子Logan.Archie和Chuck. 他有一所很大的房子,同住的有男

  • [技术讨论]看了73篇极限编程论文后的感觉和一点总结

    最近两周的时间里,我看了73篇极限编程的论文,其中68篇是主要写结对编程的论文. 在这些论文中,我看到了各个国家的文章,欧洲,北美,澳洲,少量亚洲的论文,其中看到了大概四五篇来自国内大学与国外大学或者研究机构合作的论文. 国内这些大学的名字就不提了. 我看到美国的一些结对编程的论文参与的学生数量从几十到一千多人,部分研究时间长达三年.当然那个大学在这三年间发表了多篇论文,其中关于结对编程方面的论文有五篇之多. 看到了一篇国内某大学一学生与国外一华人留学生合写的论文,后来在另一篇论文中发现了内容与

  • 看了这篇文章,终于明白了日本人是怎么取名的(转)........

    看了这篇文章,终于明白了日本人是怎么取名的........ 一个韩国导游公开了日本一个惊天大秘密(转) 日本人好战. 古时 几乎所有的少壮男丁都被征召去当兵打仗 根本没有时间结婚生子 所以 人丁越来越少 当时 一个国主就出了一个国策 让所有的男人 不论何时何地 都可以随便跟任何女人发生关系 来保持人口的出生率 所以 在休战期间 日本女人都习惯了「无论何时何地」的那种方式 乾脆 就背著枕头.被单出门 後来 就成了现在所谓的「和服」 很多女人 被人「无论何时何地」後 对方都来不及告知姓氏 就又去打仗

  • 华谊姐妹家政带你看古代女人残忍的避孕法

    华谊姐妹家政带你看古代女人残忍的避孕法 现代的避孕药具出现可以说给男女性都带来了很多方便和"性"福,宫内节育器.口服避孕...这是育龄女性最关心的问题之一,深圳保姆华谊姐妹家政一起与你看看古代青楼女子她们是如何采取残忍避孕措施的呢? 1."凉药" 即使是现在,也没有百分之百有效的避孕措施,韦小宝就是避孕失败的产物.过去许多青楼女子在从良后,都是终身不育的,就是长期饮用破坏生育能力的一种汤药,以致绝育,说是服用被称做"凉药"的一种可以避孕的药,也含

  • 深圳月嫂带你看怀孕期间能享有什么权益?

    深圳月嫂带你看怀孕期间能享有什么权益? 妊娠期权益 有两种政府补贴:法定产假工资(SMP)和生育津贴.有些雇主所提供的产假权益,远远超过法律要求的基本范围.所有公民咨询局或社会保障局都会可查明你所拥有的特定权利.妊孕妇所享有的特定权力和权益,取决于她的个人情况. 谁能领产假工资? 深圳月嫂华谊姐妹提示:怀孕期间和生产后12周内,你还能得到免费处方药和牙齿治疗,如果你属于低收入人群,你和不满5岁的孩子还可以享受给你的免费牛奶和维生素. 如果你签有固定劳动合同,或受雇于全职或兼职工作超过六个月,你就

  • 北京中硕带您看清贷款骗局

    向银行借款是很多人最乐意做的事情,因为他们觉得借银行的钱最可靠,甚至有的时候你不偿还银行的钱,也不会导致什么大的灾难,而更多的人渴望通过银行的钱,让自己的企业或者家庭缓解困境,但是谁也不想碰到贷款骗子,今天北京中硕带您看清常见的贷款骗局. "大公司"下的贷款骗局 很多公司看到了互联网火热,也展开了所谓的借款,放款等业务,让一些渴望快速借贷的人上钩,而这其中,就不乏一些骗子公司. 无抵押贷款骗子往往打着"大公司"."全国业务"的旗号,乍一看颇有气势

  • 请看过这篇文章的人懂得什么叫尊重

    是责任促使我写下这篇平凡但是不平庸的文字. 今天碰到的几件连串的小事,勾起了很多我灵魂深处的东西,那些埋藏的感情和知觉都被唤醒. 我知道这些事情足够的平凡,可是其中蕴含的感情却是那么的沉重. 不是压抑,而是倔强的感触. 今天比较繁忙,上午上了半天的课,下午上了会网就回学校写作业了,之所以留在学校是因为要帮助组里的同学做实验.结果到晚上九点钟才拖着疲惫的身躯从学校出来.准备走的时候,发现车子爆胎了,可能是白天被太阳暴晒的缘故.当时有些不知所措.只能推着车子往回走.离家大概还有七八站地吧,内心还是有

  • 手把手教你把Vim改装成一个IDE编程环境(图文) &amp;lt;看了这篇文章才知道,以前从来不曾用过vim&amp;gt;

    手把手教你把Vim改装成一个IDE编程环境(图文) By: 吴垠 Date: 2007-09-07 Version: 0.5 Email: lazy.fox.wu#gmail.com Homepage: http://blog.csdn.net/wooin Copyright: 该文章版权由吴垠和他可爱的老婆小包子所有.可在非商业目的下任意传播和复制.对于商业目的下对本文的任何行为需经作者同意. 联系方式:lazy.fox.wu#gmail.com 1 写在前面 Linux下编程一直被诟病的一点

Tags: