本篇文章将从EventBus的常用使用步骤去逐步分析它的源码和内部实现,其中会额外提到粘性事件、观察者模式和接口回调的区别以及EventBus线程调度相关知识。

下面就从以下的使用流程去逐步分析EventBus的源码。

初始化->注册->发送事件->解除注册

初始化

使用EventBus时我们一般通过调用getDefault来获取EventBus对象,EventBus的获取实例方法仍然是一个单例来实现,再继续看,发现是熟悉的双重锁,但是接下来的构造函数和我们通常写的会有不同,们一我般会把构造函数用private封闭,但是这里并没有修改修饰符,而是直接调用了它的有参构造函数,传入一个builder进行构造,接着就成功完成了EventBus的初始化。

这里之所以不修改修饰符,源码注释里也给了说明

Creates a new EventBus instance; each instance is a separate scope in which events are delivered.To use a central bus, consider {@link #getDefault()}.
    //双重锁单例public static EventBus getDefault() {if (defaultInstance == null) {synchronized (EventBus.class) {if (defaultInstance == null) {defaultInstance = new EventBus();}}}return defaultInstance;}//这里并没有对构造函数封闭,和一般的单例不同,它允许直接调用构造函数创建实例//this调用它自己的有参构造函数,并把DEFAULT_BUILDER当参数传入public EventBus() {this(DEFAULT_BUILDER);}//默认builderprivate static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();//使用建造者模式 创建不同的EventBus实例EventBus(EventBusBuilder builder) {logger = builder.getLogger();...}

注册

首先使用反射方法获取到传入对象的类型,接着通过subscriberMethodFinder的查找方法去寻找该订阅者希望订阅的方法,接着锁住当前实例,不断去遍历刚才找出的方法,调用subscribe去进行订阅。

所以整个注册过程就是 找出想要订阅的方法->为当前对象类型订阅这些方法

希望订阅的方法:我把如下这类似方法在当前阶段称为希望订阅的方法是因为在这时,这些方法并没有成功进行订阅,在执行EventBus中的订阅逻辑后才能将其称为订阅方法。

@Subscribe(threadMode = ThreadMode.MAIN)
public void XXX(MessageEvent messageEvent) {...
}

注册逻辑:

    public void register(Object subscriber) {//首先获取到传入的对象的类型Class<?> subscriberClass = subscriber.getClass();//找出当前订阅者订阅了的所有方法并存入集合List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);synchronized (this) {//遍历刚找出的方法 依次进行订阅for (SubscriberMethod subscriberMethod : subscriberMethods) {subscribe(subscriber, subscriberMethod);}}}"其中SubscriberMethod记录了以下几种信息"包括线程模式、对象类型、优先级、粘性等final Method method;final ThreadMode threadMode;final Class<?> eventType;final int priority;final boolean sticky;

简单了解了下它的注册方法,接着我们去看看它是怎样找出订阅方法的还有就是怎么进行订阅的,这就对应着findSubscriberMethodssubscribe 这两个方法。

  • findSubscriberMethods

    //方法缓存(使用的是ConcurrentHashMap以键值对的形式存入Map,key为传入对象类型,value为方法list)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 {subscriberMethods = findUsingInfo(subscriberClass);}//如果注册方法为空则抛出异常(说明没有用@Subscribe注解,或者没有订阅方法)//如果有的话则将其放入METHOD_CACHE中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;}}

这个查找订阅方法首先会从一个方法缓存中去获取当前传入类型的所有订阅方法,如果方法列表已经存在不为空则直接返回已有的方法列表,然后根据ignoreGeneratedIndex的值来判断用哪个方法去查找订阅方法,接着就将查找到的方法放入方法缓存list里面。

在上面findSubscriberMethods中其实是有两类找出subscriberMethods的方法,一类是从METHOD_CACHE中去查找Map当中是否已经存有注册方法,第二类就是通过反射用那两种方法去找,但实际上都会调用到findUsingReflectionInSingleClass去查找相关订阅方法。

首先看下findUsingReflection方法

    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {FindState findState = prepareFindState();findState.initForSubscriber(subscriberClass);while (findState.clazz != null) {//调用查找订阅方法的方法findUsingReflectionInSingleClass(findState);//依次获取该class的父类,向上遍历并且向上move的过程会跳过系统classfindState.moveToSuperclass();}return getMethodsAndRelease(findState);}void moveToSuperclass() {if (skipSuperClasses) {clazz = null;} else {clazz = clazz.getSuperclass();String clazzName = clazz.getName();/** Skip system classes, this just degrades performance. */if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {clazz = null;}}}

在上述findUsingReflection当中,我们能够看到非常关键的一点,那就是EventBus支持事件订阅的继承。

接着在findUsingReflectionInSingleClass这个方法当中,整个过程是通过反射去判断方法是否符合“要求”,包括作用域(public)、形参长度、是否有注解 ,在经过一系列的判断之后将所有满足条件的method存入subscriberMethods这个ArrayList当中。

    private void findUsingReflectionInSingleClass(FindState findState) {Method[] methods;try {// This is faster than getMethods, especially when subscribers are fat classes like Activitiesmethods = findState.clazz.getDeclaredMethods();} catch (Throwable th) {// Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149methods = findState.clazz.getMethods();findState.skipSuperClasses = true;}for (Method method : methods) {"判断作用域(是否为public)"//先获取该方法的修饰符如果为public且不为abstract、static才能通过筛选int modifiers = method.getModifiers();//按位与操作符相同为1不同为0if ((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) {//取得Event事件,调用checkAdd进行判断"下面的checkAdd源码"Class<?> eventType = parameterTypes[0];if (findState.checkAdd(method, eventType)) {//获取到当前注解指定的线程模式(threadMode)ThreadMode threadMode = subscribeAnnotation.threadMode();//将这个订阅方法加入ArrayList中存储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");}}}//这个检测方法主要由两级检测,第一级速度快,单纯的对event的类型进行检测//第二级是对其签名的检测boolean checkAdd(Method method, Class<?> eventType) {// 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.// Usually a subscriber doesn't have methods listening to the same event type.Object existing = anyMethodByEventType.put(eventType, method);if (existing == null) {return true;} else {if (existing instanceof Method) {if (!checkAddWithMethodSignature((Method) existing, eventType)) {// Paranoia checkthrow new IllegalStateException();}// Put any non-Method object to "consume" the existing MethodanyMethodByEventType.put(eventType, this);}return checkAddWithMethodSignature(method, eventType);}}

接着是findUsingInfo这个方法

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

到现在,我们已经找出了该对象(如:activity)所有订阅方法(根据作用域、形参数量、注解等筛选出的),接着我们要做的就是将这些方法进行订阅(subscribe)。

  • subscribe方法

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {Class<?> eventType = subscriberMethod.eventType;//将所有的观察者和订阅方法封装成一个subscription对象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();//根据方法的优先级来让其加入到subscription列表当中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);"对sticky的event的处理"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);}}}

在subscribe方法中,我们看到了EventBus对粘性事件(Sticky = true)的处理,所以这里有必要提一下粘性事件相关概念。我们看其中的逻辑,都调用了checkPostStickyEventToSubscription方法,接着跟进这个方法,我们看到的是postToSubscription这个在发送过程中起关键作用的代码,事件发送(post)我们等下再分析,但是我们必须弄清楚为什么在注册过程当中,一般事件都是将其存入事件列表就行了,为什么粘性事件在注册阶段就进行了事件发送的操作。所以我们得首先了解什么是粘性事件,以及为什么需要此类事件。

粘性事件:

粘性事件和一般的事件有所不同,普通的事件通常是先注册再发布,而粘性事件一般是先发布再订阅。那么为什么会需要用到粘性事件呢?设想一个例子,我们要从Activity A往Activity B发送事件,让B根据A的消息执行相关逻辑,那么在这个例子当中,我们的B就是一个订阅者,A就是发布者,但如果B这个订阅者在A发布之后还未启动,那它是收不到这个消息的,所以粘性事件的存在就解决了这个问题,反正我这个事件先发布在这里,你有空了就来订阅就行,所以粘性事件也是解决了这种常见的问题。

所以,我们在了解了粘性事件的作用和工作原理之后,我们再来看注册这里的代码就能够理解了,就对应着上面那句话“为什么粘性事件在注册阶段就进行了事件发送的操作”,我们看上面的代码对粘性事件的处理部分可以发现,它首先取出了所有的粘性事件,因为它不知道你要的是哪一个粘性事件,所以得全部取出来遍历去判断,如果判断成功之后呢,这里就进入到了普通事件到发布阶段要做的操作,所以也就能理解为什么粘性事件在注册阶段就完成了事件的发送。

在执行完了subscribe方法之后,我们就相应的完成了事件的订阅。

接着进入更为关键的事件发送(post)流程。

发送事件

我们还是从使用去看,以下是最简单的post event的代码。

EventBus.getDefault().post(messageEvent);

在事件发送的这个过程当中

    public void post(Object event) {//PostingThreadState当中维护了线程相关参数//currentPostingThreadState是一个Thread Local,这里对于Thread Local而言设置和读取都快很多PostingThreadState postingState = currentPostingThreadState.get();List<Object> eventQueue = postingState.eventQueue;eventQueue.add(event);if (!postingState.isPosting) {postingState.isMainThread = isMainThread();postingState.isPosting = true;if (postingState.canceled) {throw new EventBusException("Internal error. Abort state was not reset");}try {//每次取出事件序列中的第一个,接着把postingState参数传入while (!eventQueue.isEmpty()) {postSingleEvent(eventQueue.remove(0), postingState);}} finally {postingState.isPosting = false;postingState.isMainThread = false;}}}

在取出当前线程相关参数之后,取得当前发送线程中的事件队列,把传入的事件加入到事件队列当中,如果还有未发送的事件,则每次移除事件队列中的第一个事件并进行发送,接着在发送完成之后重置标志位,使当前线程中的事件重新成为可发送状态。

下面来看下postSingleEvent方法

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {Class<?> eventClass = event.getClass();boolean subscriptionFound = false;if (eventInheritance) {//这里获取了所有父类class的集合List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);int countTypes = eventTypes.size();//循环该event所有的父类,并依次加入postfor (int h = 0; h < countTypes; h++) {Class<?> clazz = eventTypes.get(h);subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);}} else {subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);}if (!subscriptionFound) {if (logNoSubscriberMessages) {logger.log(Level.FINE, "No subscribers registered for event " + eventClass);}if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&eventClass != SubscriberExceptionEvent.class) {post(new NoSubscriberEvent(this, event));}}}

在上面的postSingleEvent方法中,主要是对当前传入的event的父类进行获取,如果有就遍历并找到其所有父类,再遍历将每个父类当做一个单独的事件进行传入并发送,如果没有就直接将其当前类型传入进行发送。所以接下来进入到了postSingleEventForEventType这个方法当中。

postSingleEventForEventType方法

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {CopyOnWriteArrayList<Subscription> subscriptions;synchronized (this) {//根据event的class类型从subscription中取出对应的封装好的订阅事件subscriptions = subscriptionsByEventType.get(eventClass);}if (subscriptions != null && !subscriptions.isEmpty()) {for (Subscription subscription : subscriptions) {postingState.event = event;postingState.subscription = subscription;boolean aborted = false;try {//调用该方法对事件进行发送postToSubscription(subscription, event, postingState.isMainThread);aborted = postingState.canceled;} finally {//重置标志位postingState.event = null;postingState.subscription = null;postingState.canceled = false;}if (aborted) {break;}}return true;}return false;}

上面方法的逻辑主要是根据上一步操作传入的class的类型去查找之前已经订阅好并进行封装了的subscription,并为当前发送状态赋值,再调用postToSubscription对相应事件进行发送。

postToSubscription方法

    //根据线程模型选择执行对应逻辑private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {switch (subscription.subscriberMethod.threadMode) {case POSTING://如果是POSTING则直接通过反射invoke去调用该对应的订阅方法invokeSubscriber(subscription, event);break;case MAIN://如果是主线程也同样直接调用对应订阅方法,如果不是则调用mainThreadPoster的enqueueif (isMainThread) {invokeSubscriber(subscription, event);} else {mainThreadPoster.enqueue(subscription, event);}break;case MAIN_ORDERED:if (mainThreadPoster != null) {mainThreadPoster.enqueue(subscription, event);} else {// temporary: technically not correct as poster not decoupled from subscriberinvokeSubscriber(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);}}

在这个方法当中终于看到了eventBus对线程模型的相关逻辑。

首先,如果是POSTING这种线程模型,因为处理事件和发送事件的线程都是在同一个线程当中的,所以并不需要做特殊处理,直接调用invokeSubscriber,这里面又通过反射去调用了对应的订阅方法。

接着在MAIN中,如果是主线程的话,同样直接调用相关订阅方法,那么如果不是在主线程当中呢,这里就涉及到了EventBus中对线程调度问题,所以要弄明白它是怎样实现线程调度的话就得先看下mainThreadPoster和它的enqueue方法做了什么。首先这个poster是一个接口,但是在它的实现当中有一个HandlerPoster是继承自Handler并且实现了Poster这个接口的,所以初步猜想EventBus的线程调度应该是借助了Handler来完成的,其中的enqueue方法中完成了消息的发送,自身的handleMessage完成对消息的处理。

handlerPoster源码

    public void enqueue(Subscription subscription, Object event) {PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);synchronized (this) {queue.enqueue(pendingPost);if (!handlerActive) {handlerActive = true;//发送消息if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}}}}//处理消息@Overridepublic void handleMessage(Message msg) {boolean rescheduled = false;try {long started = SystemClock.uptimeMillis();while (true) {PendingPost pendingPost = queue.poll();if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = queue.poll();if (pendingPost == null) {handlerActive = false;return;}}}//通过反射去调用订阅方法eventBus.invokeSubscriber(pendingPost);long timeInMethod = SystemClock.uptimeMillis() - started;if (timeInMethod >= maxMillisInsideHandleMessage) {if (!sendMessage(obtainMessage())) {throw new EventBusException("Could not send handler message");}rescheduled = true;return;}}} finally {handlerActive = rescheduled;}}

其次是BACKGROUND这个线程模型,如果是在主线程则调用backgroundPoster的enqueue方法,如果不是则直接通过反射qui调用其方法,这个backgroundPoster实现了Runnable和Poster接口的一个类,它其中提供的enqueue方法,它把pendingPost交给了线程池去处理,被调用后会执行run的回调,run方法中又是通过反射去调用了订阅方法。

backgroundPoster源码

    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);}}}@Overridepublic void run() {try {try {while (true) {从队列中取出pendingPostPendingPost pendingPost = queue.poll(1000);if (pendingPost == null) {synchronized (this) {// Check again, this time in synchronizedpendingPost = queue.poll();if (pendingPost == null) {executorRunning = false;return;}}}//通过反射去处理该事件eventBus.invokeSubscriber(pendingPost);}} catch (InterruptedException e) {eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);}} finally {executorRunning = false;}}

最后是ASYNC这种线程模型,这个它和backgound的第二种是一样的,也是通过线程池去执行,最后通过反射去调用。

解除注册

首先也是了解下解除注册的常用流程,这里是直接调用了EventBus的unregister方法完成解除工作,我们先大概想一下,解除注册无非就是把该清除的清除,该重置的重置,大体应该就是这样,那么接下来去看看EventBus内部具体是怎么去解除注册的。

        if(EventBus.getDefault().isRegistered(this)) {EventBus.getDefault().unregister(this);}

在看解除注册的源码之前,我们先回顾下EventBus中的两个参数,方便我们理解下解除注册流程到底干了什么。

    "subscriptionsByEventType"private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;Class<?> eventType = subscriberMethod.eventType;subscriptionsByEventType = new HashMap<>();subscriptionsByEventType.put(eventType, subscriptions);"typesBySubscriber"private final Map<Object, List<Class<?>>> typesBySubscriber;typesBySubscriber = new HashMap<>();typesBySubscriber.put(subscriber, subscribedEvents);

第一个是subscriptionsByEventType,HashMap(key:订阅方法的事件类型;value:泛型为Subscription的一个list),其中Subscription中包含有订阅对象和订阅方法的信息,具体订阅方法有方法,线程模型,事件类型,优先级,是否粘性事件等字段信息。

第二个是typesBySubscriber,HashMap(key:订阅对象的class类型;value:泛型为各种事件类型的list)

unregister源码

/** 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 {logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());}}

在unregister当中,首先通过订阅对象的class类型取出对应的该class订阅了的各类事件类型,然后依次进入unsubscribeByEventType方法去。

unsubscribeByEventType源码

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

这里根据事件类型从subscriptionsByEventType中获取到对应的subscription,接着就是去将subscriptions的值依次remove掉,就完成了解除注册。

总结

整体而言,EventBus是一个基于观察者模式而构建的框架,这里提到观察者模式,稍微提一下个人觉得和接口回调的区别,在开始时我觉得都差不多,没什么差别,其实现在看来我觉得接口回调可以看做观察者模式的一个特例吧,也就是接口回调相当于是只有一个观察者的观察者模式,所以我认为区别就在于观察者模式是一个一对多的关系,所有观察者接收到消息后都能够自动更新。而且EventBus在观察者这个模式上又进行了上层的一个处理,EventBus将事件和事件处理类大幅解耦,让我们的使用和代码整洁度都有了比较好的一个提升。

EventBus源码分析 1相关推荐

  1. EventBus源码分析

    简介 前面我学习了如何使用EventBus,还有了解了EventBus的特性,那么接下来我们一起来学习EventBus的源码,查看EventBus的源码,看看EventBus给我们带来什么惊喜以及编程 ...

  2. Android主流三方库源码分析(九、深入理解EventBus源码)

    一.EventBus使用流程概念 1.Android事件发布/订阅框架 2.事件传递既可用于Android四大组件间通信 3.EventBus的优点是代码简洁,使用简单,事件发布.订阅充分解耦 4.首 ...

  3. Android 源码分析之 EventBus 的源码解析

    1.EventBus 的使用 1.1 EventBus 简介 EventBus 是一款用于 Android 的事件发布-订阅总线,由 GreenRobot 开发,Gihub 地址是:EventBus. ...

  4. Android—EventBus使用与源码分析

    EventBus 安卓事件发布/订阅框架 事件传递既可用于Android四大组件间通讯 EventBus的优点是代码简洁,使用简单,并将事件发布和订阅充分解耦 在onStart进行注册,onStop进 ...

  5. 【转】ABP源码分析二十五:EventBus

    IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...

  6. ABP源码分析二十五:EventBus

    IEventData/EventData: 封装了EventData信息,触发event的源对象和时间 IEventBus/EventBus: 定义和实现了了一系列注册,注销和触发事件处理函数的方法. ...

  7. Android 框架学习2:源码分析 EventBus 3.0 如何实现事件总线

    Go beyond yourself rather than beyond others. 上篇文章 深入理解 EventBus 3.0 之使用篇 我们了解了 EventBus 的特性以及如何使用,这 ...

  8. EventBus源码解析

    前面一篇文章讲解了EventBus的使用,但是作为开发人员,不能只停留在仅仅会用的层面上,我们还需要弄清楚它的内部实现原理.所以本篇博文将分析EventBus的源码,看看究竟它是如何实现"发 ...

  9. 【EventBus】EventBus 源码解析 ( 取消订阅 )

    文章目录 一.取消订阅 二.取消订阅 unsubscribeByEventType 方法 一.取消订阅 [EventBus]EventBus 使用示例 ( 最简单的 EventBus 示例 ) 示例中 ...

最新文章

  1. linux redhat、ubuntu系统 docker启动、停止命令
  2. python中sort返回值_Python函数你真的都学会了吗?来看看这篇Python高阶函数!
  3. 性冷淡风的麻将,获红点奖!网友:没有烟火气了
  4. AtCoder Regular Contest 120 C - Swaps 2 线段树模拟
  5. RTX5 | STM32H743+CubeMX+RTX5+两路FDCAN模板
  6. 计算机硬件格式,排版格式要求_计算机硬件及应用_IT/计算机_资料
  7. Jedis 常用API使用
  8. python处理can协议文件_二、如何解决:python:Can't reopen .pyc file
  9. 2012腾讯实习招聘笔试附加题1求解方法
  10. linux密码安全加固技术-CKEY动态密码技术【顶】
  11. 矩阵转置算法 oracle,请编写程序fun,函数的功能是:实现B=A+Aˊ,即把矩阵A加上A的转置,存放在矩阵B中。计算结果在main函...
  12. 齐了!百度、腾讯、滴滴、抖音的技术大佬都来了
  13. JPA设置表名和实体名,表字段与实体字段的对应
  14. 2021年底跨平台技术比较和选型指南(也许是最全的)
  15. windows开始菜单打不开,搜索打不开
  16. Linux-tar打包与解压命令
  17. Win系统蓝牙设备删除失败 - 解决方案
  18. python,java,go语言之间的区别!
  19. 电子邮箱哪家最安全,各家电子邮箱安全性盘点!
  20. 你不知道的关于计算机大师Dijkstra的事情

热门文章

  1. Go语言小程序开发快速入门——一、用Gin框架实现简单的信息获取
  2. STM32开发 -- 低功耗模式详解
  3. 笔记2:VC++ socket通信实例
  4. 平面设计师经常去的网站——设计灵感类
  5. java正常运行但javac报错
  6. 【threeJS】纹理贴图
  7. 【CV】膨胀卷积详解以及时间卷积网络TCN论文笔记和源码实现
  8. 智能合约部署Error: exceeds block gas limit undefined
  9. 如何使用ABBYY Vantage的“增值税发票” 技能, 如何使用处理银行转账收据。
  10. 深度强化学习笔记(二)——Q-learning学习与二维寻路demo实现