EventBus 3.0源码简要分析

一、简介
EventBus(3.0)、RxJava(RxAndroid)都是常用的解耦框架,目的都是为了让代码中的事件处理独立,减少类与类之间的依赖和耦合。
附上EventBus源码地址: https://github.com/greenrobot/EventBus
二、EventBus(3.0)
(1)EventBus(3.0)介绍
首先介绍一个EventBus 3.0 和 2.x的区别
主要区别在订阅函数的不同
EventBus2.x中只暴露了四个方法供用户调用,分别是
  • onEvent:该事件在哪个线程发布出来的,onEvent就会在这个线程中运行,也就是说发布事件和接收事件线程在同一个线程。使用这个方法时,在onEvent方法中不能执行耗时操作,如果执行耗时操作容易导致事件分发延迟。
  • onEventMainThread:不论事件是在哪个线程中发布出来的,onEventMainThread都会在UI线程中执行,接收事件就会在UI线程中运行,这个在Android中是非常有用的,因为在Android中只能在UI线程中跟新UI,所以在onEvnetMainThread方法中是不能执行耗时操作的。
  • onEventBackgroundThread:如果事件是在UI线程中发布出来的,那么onEventBackground就会在子线程中运行,如果事件本来就是子线程中发布出来的,那么onEventBackground函数直接在该子线程中执行。
  • onEventAsync:无论事件在哪个线程发布,都会创建新的子线程在执行onEventAsync.

EventBus3.0中必须使用注解,例如:

@Subscribe(threadMode = ThreadMode.Async, sticky = true, priority = 1)
public void firstEvent(FirstEvent event) {
    Log.e("TAG", "Async" + Thread.currentThread().getName());
}

好处在于订阅函数可以随便起名字,其他与2.x没什么不同。这里Subscribe中的key需要解释一下含义,Subscribe中可以传三种值:
  • ThreadMode:这是个枚举,有四个值,决定订阅函数在哪个线程执行
  • PostThread:事件发送者在哪个线程就执行在哪个线程。同2.x中的onEvent方法,默认值就是这个
  • MainThread:订阅函数一定执行在主线程。同onEventMainThread方法
  • BackgroundThread:如果是事件从子线程发出,订阅函数就执行在那个子线程,不会创建新的子线程;如果主线程发出事件,则创建子线程。同onEventBackgroundThread方法
  • Async:一定创建子线程。同onEventAsync方法。
  • sticky:默认为false,如果为true,当通过postSticky发送一个事件时,这个类型的事件的最后一次事件会被缓存起来,当有订阅者注册时,会把之前缓存起来的这个事件直接发送给它。使用在比如事件发送者先启动了,订阅者还没启动的情况。
  • priority:默认值为0。订阅了同一个事件的订阅函数,在ThreadMode值相同的前提下,收到事件的优先级。
EventBus 3.0这样修改有一个很好的好处是名字可以随便修改,可以根据功能命名,不会因为同名方法造成混乱。
(2)使用
下面展示一个简单的demo
public class MainActivity extends AppCompatActivity {
    private View mEventBusView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mEventBusView = findViewById(R.id.event_bus_view);

        EventBus.getDefault().register(this);

        mEventBusView.setOnClickListener(new View.OnClickListener(){

            @Override
            public void onClick(View v) {
                EventBus.getDefault().post("this is the event bus test!");
            }
        });
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMessageEvent(String message) {
        Toast.makeText(this, message, Toast.LENGTH_SHORT).show();
    }

    public void onDestroy() {
        super.onDestroy();
        EventBus.getDefault().unregister(this);
    }
}
使用非常简单,因为EventBus本来是用来解耦的,其他的还有几种方法,具体使用可以参见官网: http://greenrobot.org/eventbus/documentation/ ,也可以直接参考一些博客的例子,网上都有非常多的demo。
(3)源码分析
EventBus使用非常简单,但是使用的时候,会有疑问了,怎么调用的,比如上面的onMessageEvent这个接口,怎么调用的,完全不明白。首先看一个官网给的图,EventBus采用的是订阅-发布的消息模式(或者说是观察者模式)。


其次来看一下实际怎么实现这种消息模式的。从上面简单的demo中,可以看到几个关键的东西,register、unregister和post接口,还有一个不知道怎么被调用的onMessageEvent接口。
a.EventBus初始化
EventBus(EventBusBuilder builder) {
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}
EventBus的线程模式有POSTING、MAIN、BACKGROUND、ASYNC四种,其中posting是表示在同一个线程调用和执行;MAIN是不管在哪个线程调用都在主线程执行;BACKGROUND表示只要不是在主线程的话,就在当前线程执行,否则用一个新线程来执行,ASYNC表示异步执行。上面的mainThreadPoster对应了MAIN模式、backgroundPoster对应了BACKGROUND模式,asyncPoster对应了ASYNC模式,那么POSTING呢,当然不需要,因为它是哪个线程调用,就在哪个线程执行的,不需要另外的poster。
b.EventBus的register过程
public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

// Must be called in synchronized block
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    if (subscriberMethod.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);
        }
    }
}
上述的过程用简单的语句描述吧,首先通过反射的方式,获取要注册的对象的所有包含Subscriber注释的(Annotation),这里主要是这一块,等一下看具体的代码。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
然后根据不同的线程模式,同时按照不同的优先级放入到列表当中,主要是这一块代码。
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }
需要注意的是,还有一个sticky属性的,这个是指包含了sticky属性的方法,可以先通过postSticky方法抛出消息,暂存起来,后面再通过regiser注册对象的时候,包含了sticky属性的方法还可以接收到消息。
再来看刚才提到的通过反射的方式,获取要注册的对象的所有包含Subscriber注释的(Annotation)那一块的代码,这里是在类SubscriberMethodFinder中实现的。只看核心的部分代码
private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    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();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            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)) {
                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)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}
从代码中可以看到,它只获取Subsciber注释的方法,然后把构建SubscriberMethod的对象,返回结果。下面是核心部分
                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()));
                    }
有一点可能有读者注意到的是if条件
(modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0
EventBus会首先过滤掉,abstract、static、bridge、synthetic方法,后面两者是什么东东,可以看一下另一篇文章: http://blog.csdn.net/newhope1106/article/details/53897631
上面的代码也解释了上面demo中的 onMessageEvent 怎么被加进来的,下面怎么调用也可以猜到吧。
c.post方法调用
public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> 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");
        }
        try {
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}
遍历所有的Subscription,然后调用对应的接口,在调用的时候,是根据参数类型来调用的。看下面的调用就可以知道,如果没有这个参数的方法,会抛出异常。
void invokeSubscriber(Subscription subscription, Object event) {
    try {
        subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
    } catch (InvocationTargetException e) {
        handleSubscriberException(subscription, event, e.getCause());
    } catch (IllegalAccessException e) {
        throw new IllegalStateException("Unexpected exception", e);
    }
}
上文提到的不同模式的线程的执行规则,如下代码所示,POSTING模式的就在当前线程执行,MAIN模式,如果当前线程在主线程,则直接调用,否则放到通过mainThreadPoster放到主线程中执行,BACKGROUND模式则相反,ASYNC则异步执行
     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);
        }
    }


d.unregister方法调用,这一块比较简单,直接从列表中去掉即可
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());
    }
}
四、总结
EventBus作为一个非常优秀的框架,复杂都留给我框架的作者,而把简单留给了使用者,使用只需要花大概几分钟的时间阅读就可以使用这个框架,同时框架本身包含了优先级。但是有一个缺点是无差别处理,调用了post方法,会遍历所有类型的接口来处理,一个建议是希望,在post中可以加一些规则匹配,可以自定义规则,快速找到对应方法处理,而不需要这样遍历所有的方法,提高效率。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值