学习EventBus之订阅

相信做Android开发的很少有没听说过EventBus的,如果真没听过那只能说明你Out了。EventBus是针对Android平台优化了的发布/订阅事件总线,它的目的是简化Android组件以及线程间的通信,从而使代码更加简洁。

官方给的流程图

简单使用

  • 定义事件类型
    public class MessageEvent{}
  • 订阅者订阅事件
    eventBus.register(this)
  • 发送事件
    eventBus.post(messageEvent)

通过以上三步,就完成一次完整的EventBus使用流程。

其实类似这种发布/订阅的机制在生活中用的也挺多的。比如我晚上定个8点的闹钟,就相当于订阅了一个事件,这个事件就是'8点钟的闹钟'。当第二天8点闹钟响的时候,就相当于发送了事件。我就会收到这个事件,然后执行相应的动作,比如起床。

那么,既然是订阅/发布。肯定就会有订阅,有发布了。这篇文章先根据源码来研究下订阅事件的流程和原理。

一般我们都会像这样EventBus.getDefault().register(this)使用EventBus。这里首先会创建一个默认的EventBus对象,然后调用EventBus的register(Object subscriber)方法将当前类作为订阅者进行事件的订阅。

/** 
Convenience singleton for apps using a process-wide EventBus instance. 
*/
public static EventBus getDefault() {
      if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
}

可以看到,源码是通过单例模式来维护一个EventBus的实例的。从注释中看以看到,这个实例的作用于为整个进程。

这里的关键点就是register(Object subscriber)方法了,上源码:

public void register(Object subscriber) {
    register(subscriber, false, 0);
}

private synchronized void register(Object subscriber, boolean sticky, int priority) {
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        //遍历所有订阅方法进行订阅
        subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}

可以看到register(Object subscriber)方法内部调用了重载的方法register(Object subscriber, boolean sticky, int priority),所有的逻辑都在这个重载的方法内部实现。

首先执行subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()),我们跟踪到findSubscriberMethods(Class<?> subscriberClass)方法内部去看看。

//寻找subscriberClass中所有的订阅方法
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    String key = subscriberClass.getName();
    List<SubscriberMethod> subscriberMethods;
    synchronized (methodCache) {//先从缓存中根据类名找对应的方法,如果没找到说明这个类之前没有注册过
        subscriberMethods = methodCache.get(key);
    }
    if (subscriberMethods != null) {//如果找到了则直接返回,不用再查找了
        return subscriberMethods;
    }
    subscriberMethods = new ArrayList<SubscriberMethod>();
    Class<?> clazz = subscriberClass;
    HashMap<String, Class> eventTypesFound = new HashMap<String, Class>();
    StringBuilder methodKeyBuilder = new StringBuilder();
    while (clazz != null) {
        String name = clazz.getName();
        if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {//排除系统的一些类
    // Skip system classes, this just degrades performance
        break;
}

    // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
        try {
        // This is faster than getMethods, especially when subscribers a fat classes like Activities
                Method[] methods = clazz.getDeclaredMethods();//拿到订阅者中声明的所有方法
                filterSubscriberMethods(subscriberMethods, eventTypesFound, methodKeyBuilder, methods);//对方法进行过滤,找到符合订阅规则的订阅方法
            } catch (Throwable th) {
                th.printStackTrace();
                // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
                Method[] methods = subscriberClass.getMethods();
                subscriberMethods.clear();
                eventTypesFound.clear();
                filterSubscriberMethods(subscriberMethods, eventTypesFound, methodKeyBuilder, methods);
                break;
            }
            clazz = clazz.getSuperclass();
        }
        if (subscriberMethods.isEmpty()) {//查找完后如果没找到,报异常:该类订阅了EventBus,但没有对应的订阅方法
            throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                    + ON_EVENT_METHOD_NAME);
        } else {
            synchronized (methodCache) {//如果找到了,则存起来。防止重复查找。这里的key就是方法名
                methodCache.put(key, subscriberMethods);
            }
            return subscriberMethods;
        }
    }
//对方法进行过滤,筛选出符合事件接收的方法 形如 "public void onEvent**(Object event)"
private void filterSubscriberMethods(List<SubscriberMethod> subscriberMethods,
                                         HashMap<String, Class> eventTypesFound, StringBuilder methodKeyBuilder,
                                         Method[] methods) {
    for (Method method : methods) {
        String methodName = method.getName();
        if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {//方法以onEvent开头
            int modifiers = method.getModifiers(); //方法的限定符类型 public  private等
            Class<?> methodClass = method.getDeclaringClass();//声明方法的类
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                //方法是public的 且非abstract static (bridge synthetic 这两个啥意思?)
                Class<?>[] parameterTypes = method.getParameterTypes(); //方法参数类型
                if (parameterTypes.length == 1) {//方法只有一个参数
                    ThreadMode threadMode = getThreadMode(methodClass, method, methodName);
                    if (threadMode == null) {//不是订阅方法,继续迭代下一个方法
                        continue;
                    }
                    Class<?> eventType = parameterTypes[0];
                    methodKeyBuilder.setLength(0);
                    methodKeyBuilder.append(methodName);
                    methodKeyBuilder.append('>').append(eventType.getName());
                    String methodKey = methodKeyBuilder.toString();
                    Class methodClassOld = eventTypesFound.put(methodKey, methodClass);//以方法名和event类型名作为key
                                                                                        //存入订阅方法列表中
                    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
                        // Only add if not already found in a sub class
                        subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
                    } else {
                        // Revert the put, old class is further down the class hierarchy
                        eventTypesFound.put(methodKey, methodClassOld);
                    }
                }
            } else if (!skipMethodVerificationForClasses.containsKey(methodClass)) {
                Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + methodClass + "."
                        + methodName);
            }
        }
    }
}

//判断订阅方法需要在哪个线程中调用
private ThreadMode getThreadMode(Class<?> clazz, Method method, String methodName) {
    String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());//截取方法名中onEvent后的字符
                                                                                    //如 onEventMainThread -> MainThread
    ThreadMode threadMode;
    if (modifierString.length() == 0) { //方法名为onEVent 默认在PostThread中调用
        threadMode = ThreadMode.PostThread;
    } else if (modifierString.equals("MainThread")) {//方法名为onEVentMainThread 在MainThread中调用
        threadMode = ThreadMode.MainThread;
    } else if (modifierString.equals("BackgroundThread")) {//方法名为onEVentBackgroundThread 在BackgroundThread中调用
        threadMode = ThreadMode.BackgroundThread;
    } else if (modifierString.equals("Async")) {//方法名为onEVentAsync 异步调用
        threadMode = ThreadMode.Async;
    } else {//如果不是以上几个选择 认为不是订阅方法 threadMode为null
        if (!skipMethodVerificationForClasses.containsKey(clazz)) {
                throw new EventBusException("Illegal onEvent method, check for typos: " + method);
        } else {
            threadMode = null;
        }
    }
    return threadMode;
}

上面代码比较长,注释的已经很清楚了。findSubscriberMethods(Class<?> subscriberClass)执行完之后,就找到了订阅者类中所有的能够接受事件的方法并存了起来。

我们继续回到register(Object subscriber, boolean sticky, int priority)方法中,查询完订阅方法后,对所有的方法进行迭代,然后调用subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority)进行一一注册。

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
    Class<?> eventType = subscriberMethod.eventType;
    //根据事件类型取出该类事件的所有订阅者
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
    if (subscriptions == null) {//如果没有该类型的订阅者 创建一个 加入列表
        subscriptions = new CopyOnWriteArrayList<Subscription>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {//如果所有订阅者中已经包含新加入的这个订阅者,提示已经订阅了
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    // subscriberMethod.method.setAccessible(true);

    int size = subscriptions.size();
    //遍历所有的订阅者 按照优先级(Subscription的priority属性)进行排序
    for (int i = 0; i <= size; i++) {
        if (i == size || newSubscription.priority > subscriptions.get(i).priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    //取出某个订阅者中所有的订阅事件
    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<Class<?>>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (sticky) {//这块是对黏性事件的处理。目前项目中还没用过,不太理解这块
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority)方法执行完之后,所有的订阅者以及订阅者中的事件接受方法就被存起来了。

//按事件类型存放的订阅者集合
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
//按订阅者类型存放的事件集合
private final Map<Object, List<Class<?>>> typesBySubscriber;
//黏性事件(这个怎么理解?)
private final Map<Class<?>, Object> stickyEvents;

至此,事件的订阅就完成了。最终的结果就是EventBus保存了订阅者中所有符合规则(即public void onEvent***(Object event))的能接收事件的方法。当post事件的时候,就会拿着这个事件去保存的订阅者方法中去对比寻找,找到后通过反射去调用对应的方法。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容

  • 项目到了一定阶段会出现一种甜蜜的负担:业务的不断发展与人员的流动性越来越大,代码维护与测试回归流程越来越繁琐。这个...
    fdacc6a1e764阅读 3,163评论 0 6
  • 原文链接:http://blog.csdn.net/u012810020/article/details/7005...
    tinyjoy阅读 540评论 1 5
  • EventBus源码分析(一) EventBus官方介绍为一个为Android系统优化的事件订阅总线,它不仅可以很...
    蕉下孤客阅读 3,972评论 4 42
  • 先吐槽一下博客园的MarkDown编辑器,推出的时候还很高兴博客园支持MarkDown了,试用了下发现支持不完善就...
    Ten_Minutes阅读 557评论 0 2
  • Android 浅析 EventBus (二) 原理 前言 Linus Benedict Torvalds : R...
    CodePlayer_Jz阅读 3,390评论 0 6