EventBus源码解析

news2024/12/28 19:47:27

文章目录

  • 前言
  • 一、EventBus使用
  • 二、EventBus事件流程分析
    • 1.注册订阅者
    • 2.发布事件Event
    • 3.接收事件Event
    • 4.取消注册订阅者
  • 三、发送粘性事件
  • 问答
      • EventBus 以及它的优点
      • EventBus原理
    • EventBus中设计模式
      • 为什么要使用 EventBus 来替代广播呢?
      • 说下 5 种线程模式的区别
      • EventBus 是如何做到发送粘性消息的
      • EventBus2.x的版本和3.x区别
      • RxBus 与 EventBus 比较
      • 为什么会有LiveDataBus呢
      • 常用消息总线考量
  • 总结
  • 致谢


前言

EventBus是一个Android/Java平台基于订阅与发布的通信框架,可以用于Activities, Fragments, Threads, Services等组件的通信,也可以用于多线程通信。优点是开销小、使用简单、以及解耦事件发送者和接收者。

Android中除了EventBus这种应用通信方式外,还有哪些手段呢?

  • BroadcastReceiver/LocalBroadcastReceiver:跨域广播和局域广播,跨域广播可以用来做跨进程通信。局域广播也是基于Handler实现,可以用来在应用内通信。
  • Handler:这个方式的弊端在于通信消息难以管理。
  • 接口回调:接口回调的好处是比较清晰明显,但是如果涉及到大量页面的跳转或者通信场景比较复杂,这种方式就变得难以维护,耦合较高。

EventBus版本信息

官方网站:https://github.com/greenrobot/EventBus
源码版本:3.1.1


一、EventBus使用

官网图片
我们先来一个简单的Demo,从Demo入手分析事件的订阅和发布流程。

public class MainActivity extends AppCompatActivity implements View.OnClickListener{

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

        findViewById(R.id.btn_post_event).setOnClickListener(this);
    }

    @Override
    protected void onStart() {
        super.onStart();
        // 订阅事件
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onStop() {
        super.onStop();
        // 取消订阅s事件
        EventBus.getDefault().unregister(this);
    }

    // 接收事件Event
    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onEvent(Event event) {
        Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.btn_post_event:
                // 发布事件Event
                EventBus.getDefault().post(new Event("Event Message"));
                break;
        }
    }
}

具体实现细节,我们接下来深入源码中去查看。

二、EventBus事件流程分析

我们先来看一下EventBus的源码结构,如下所示:
在这里插入图片描述
主要包含了两个部分:

  • eventbus:核心库。
  • eventbus-annotation-processor:注解处理部分。

EventBus核心库调用流程如下:
在这里插入图片描述

  1. 注册订阅者。
  2. 发布事件Event。
  3. 接收事件Event。
  4. 取消注册订阅者。

1.注册订阅者

订阅事件是通过以下方法来完成的:

EventBus.getDefault().register(this);

getDefault()用来获取EventBus实例,当然你也可以通过EventBusBuilder自己构建实例。

public class EventBus {
	public void register(Object subscriber) {
			// 1. 获取订阅者的类名。
	        Class<?> subscriberClass = subscriber.getClass();
	        // 2. 查找当前订阅者的所有响应函数。
	        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
	        synchronized (this) {
	        	// 3. 循环每个事件响应函数
	            for (SubscriberMethod subscriberMethod : subscriberMethods) {
	                subscribe(subscriber, subscriberMethod);
	            }
	        }
	    }
}

SubscriberMethod用来描述onEvent()这些方法的信息,包含方法名、线程、Class类型、优先级、是否是粘性事件。

接着调用subscribe()进行事件注册,如下所示:

public class EventBus {
	// 订阅者队列
	private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
	// 后续准备取消的事件队列
	private final Map<Object, List<Class<?>>> typesBySubscriber;
	// 粘性事件队列
	private final Map<Class<?>, Object> stickyEvents;

    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    	// 事件类型(xxxEvent)
        Class<?> eventType = subscriberMethod.eventType;
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
        // 1. 获取该事件类型的所有订阅者信息。
        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();
        // 2. 按照事件优先级将其插入订阅者列表中。
        for (int i = 0; i <= size; i++) {
            if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
                subscriptions.add(i, newSubscription);
                break;
            }
        }

		// 3. 得到当前订阅者订阅的所有事件队列,存放在typesBySubscriber中,用于后续取消事件订阅。
        List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

		// 4. 是否是粘性事件,如果是粘性事件,则从stickyEvents队列中取出最后一个该类型的事件发送给订阅者。
        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);
            }
        }
    }
	    
}

Subscription包含了订阅者subscriber和订阅函数subscriberMethod两个信息。

2.发布事件Event

发送事件Event是通过以下方法完成的,如下所示:

EventBus.getDefault().post(new Event("Event Message"));
public class EventBus {

    public void post(Object event) {
    	// 1. 获取当前线程的PostingThreadState对象,该对象包含事件队列,保存在ThreadLocal中。
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        // 2. 将当前事件加入到该线程的事件队列中。
        eventQueue.add(event);

		// 3. 判断事件是否在分发中。如果没有则遍历事件队列进行实际分发。
        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                	// 4. 进行事件分发。
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }
    
}

PostingThreadState用来描述发送事件的线程的相关状态信息,包含事件队列,是否是主线程、订阅者、事件Event等信息。

然后调用postSingleEvent()进行事件分发。

public class EventBus {

    private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
        Class<?> eventClass = event.getClass();
        boolean subscriptionFound = false;
        // 1. 如果事件允许继承,则查找该事件类型的所有父类和接口,依次进行循环。
        if (eventInheritance) {
            List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
            int countTypes = eventTypes.size();
            for (int h = 0; h < countTypes; h++) {
                Class<?> clazz = eventTypes.get(h);
                // 2. 查找该事件的所有订阅者。
                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));
            }
        }
    }
    
}

然后调用postSingleEventForEventType()方法查询当前事件的所有订阅者,如下所示:

public class EventBus {

    private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
        CopyOnWriteArrayList<Subscription> subscriptions;
        synchronized (this) {
        	// 1. 获取当前事件的所有订阅者。
            subscriptions = subscriptionsByEventType.get(eventClass);
        }
        if (subscriptions != null && !subscriptions.isEmpty()) {
        	// 2. 遍历所有订阅者。
            for (Subscription subscription : subscriptions) {
                postingState.event = event;
                postingState.subscription = subscription;
                boolean aborted = false;
                try {
                	// 3. 根据订阅者所在线程,调用事件响应函数onEvent()。
                    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;
    }
		
}

调用postToSubscription()方法根据订阅者所在线程,调用事件响应函数onEvent(),这便涉及到接收事件Event的处理了,我们接着来看。

3.接收事件Event

//EventBus.java

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 {
            	//利用Handler切换到主线程,最终还是执行invokeSubscriber
                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 subscriber
                invokeSubscriber(subscription, event);
            }
            break;
        case BACKGROUND:
            if (isMainThread) {
            	//如果当前处于主线程中,将利用线程池,切换到子线程中处理,最终还是会调用invokeSubscriber
                backgroundPoster.enqueue(subscription, event);
            } else {
            	//如果当前处于子线程,则直接在该子线程中处理事件
                invokeSubscriber(subscription, event);
            }
            break;
        case ASYNC:
        	//无论处于什么线程,最终都是利用线程池,切换到子线程中处理,最终还是会调用invokeSubscriber
            asyncPoster.enqueue(subscription, event);
            break;
        default:
            throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

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);
    }
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void onEvent(Event event) {
    Toast.makeText(this, event.getMessage(), Toast.LENGTH_SHORT).show();
}

如上所示,onEvent函数上是可以加Subscribe注解了,该注解标明了onEvent()函数在哪个线程执行。主要有以下几个线程:

  • PostThread:默认的 ThreadMode,表示在执行 Post 操作的线程直接调用订阅者的事件响应方法,不论该线程是否为主线程(UI 线程)。当该线程为主线程时,响应方法中不能有耗时操作,否则有卡主线程的风险。适用场景:对于是否在主线程执行无要求,但若 Post 线程为主线程,不能耗时的操作;
  • MainThread:在主线程中执行响应方法。如果发布线程就是主线程,则直接调用订阅者的事件响应方法,否则通过主线程的 Handler 发送消息在主线程中处理,调用订阅者的事件响应函数。显然,MainThread类的方法也不能有耗时操作,以避免卡主线程。适用场景:必须在主线程执行的操作;
  • MAIN_ORDERED:无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。
  • BackgroundThread:在后台线程中执行响应方法。如果发布线程不是主线程,则直接调用订阅者的事件响应函数,否则启动唯一的后台线程去处理。由于后台线程是唯一的,当事件超过一个的时候,它们会被放在队列中依次执行,因此该类响应方法虽然没有PostThread类和MainThread类方法对性能敏感,但最好不要有重度耗时的操作或太频繁的轻度耗时操作,以造成其他操作等待。适用场景:操作轻微耗时且不会过于频繁,即一般的耗时操作都可以放在这里;
  • Async:不论发布线程是否为主线程,都使用一个空闲线程来处理。和BackgroundThread不同的是,Async类的所有线程是相互独立的,因此不会出现卡线程的问题。适用场景:长耗时操作,例如网络访问。

这里线程执行和EventBus的成员变量对应,它们都实现了Runnable与Poster接口,Poster接口定义了事件排队功能,这些本质上都是个Runnable,放在线程池里执行,如下所示:

private final Poster mainThreadPoster;
private final BackgroundPoster backgroundPoster;
private final AsyncPoster asyncPoster;
private final SubscriberMethodFinder subscriberMethodFinder;
private final ExecutorService executorService;

4.取消注册订阅者

取消注册订阅者调用的是以下方法:

EventBus.getDefault().unregister(this);

具体如下所示:

//EventBus.java

    public synchronized void unregister(Object subscriber) {
    	// 1. 获取当前订阅者订阅的所有事件类型。
        List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
        if (subscribedTypes != null) {
        	// 2. 遍历事件队列,解除事件注册。
            for (Class<?> eventType : subscribedTypes) {
                unsubscribeByEventType(subscriber, eventType);
            }
            // 3. 移除事件订阅者。
            typesBySubscriber.remove(subscriber);
        } else {
            logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

调用unsubscribeByEventType()移除订阅者,如下所示:

//EventBus.java

private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
	// 1. 获取所有订阅者信息。
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
    	// 2. 遍历订阅者
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            // 3. 移除该订阅对象。
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}

以上便是EventBus核心的实现,相对还是比较简单的。

三、发送粘性事件

如果你在发送普通事件前没有注册过订阅者,那么这时你发送的事件是不会被接收执行的,这个事件也就被回收了。

而粘性事件就不一样了,你可以在发送粘性事件后,再去注册订阅者,一旦完成订阅,这个订阅者就会接收到这个粘性事件。 与发送普通事件不同,粘性事件使用postSticky()方法来发送:

EventBus.getDefault().postSticky(new MessageEvent("Hello everyone!"));

让我们从源码中看看,是如何实现的:

/**
 * 用来存放粘性事件
 *
 * key -> 粘性事件的类对象
 * value -> 粘性事件
 */
private final Map<Class<?>, Object> stickyEvents;

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

用了一个stickyEvents集合来保存粘性事件,存入后,与普通事件一样同样调用post()方法。
这里有个疑问,针对上面的使用场景,我先发送粘性事件,然后再去注册订阅,这时执行post方法去发送事件,根本就没有对应的订阅者啊,肯定是发送失败的。所以,细想一下,想达到这样效果,订阅者注册订阅后应该再将这个存入下来的事件发送一下

带着这个疑问,我们回到register -> subscribe方法:

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    //通过订阅方法获得事件类型参数
    Class<?> eventType = subscriberMethod.eventType;
    //通过订阅者与订阅方法来构造出一个 订阅对象
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    ...
    .省略部分代码.
	...
    //如果订阅方法支持粘性事件
    if (subscriberMethod.sticky) {
        //是否考虑事件类的层次结构,默认为true
        if (eventInheritance) {
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                //eventType 是否是 candidateEventType 的父类
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    //检查发送粘性事件
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            //根据事件类型获取粘性事件
            Object stickyEvent = stickyEvents.get(eventType);
            //检查发送粘性事件
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

/**
 *检查发送粘性事件
 */
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    //如果粘性事件不为空,发送事件
    if (stickyEvent != null) {  
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}

果真,订阅者在注册订阅方法中,如果当前订阅方法支持粘性事件,则会去stickyEvents集合中查件是否有对应的粘性事件,如果找到粘性事件,则发送该事件。


问答

EventBus 以及它的优点

EventBus 是一个 Android 事件发布/订阅框架,主要用来简化 Activity、Fragment、Service、线程等之间的通讯。
优点:开销小、使用简单、以及解耦事件发送者和接收者。
缺点:原理实现复杂,无法混淆,需要手动绑定生命周期(所以后面有了LiveDataBus)。

EventBus原理

  • 注册: 通过反射获取注册类上所有的订阅方法,然后将这些订阅方法进行包装保存到 subscriptionsByEventType 集合。这里还用 typesBySubscriber 集合保存了事件类型集合,用来判断某个对象是否注册过。
  • 解注册: 注册的时候使用 subscriptionsByEventType 集合保存了所有订阅方法信息,使用 typesBySubscriber 集合保存了所有事件类型。那么解注册的时候就是为了移除这两个集合中保存的内容。
  • 发送普通事件: 从 subscriptionsByEventType 集合中取出所有订阅方法,然后根据线程模式判断是否需要切换线程,不需要则直接通过反射调用订阅方法;需要则通过 Handler 或线程池切换到指定线程再执行。
  • 发送粘性事件: 发送粘性事件的的时候,首先会将事件保存到 stickyEvents 集合,等到注册的时候判断如果是粘性事件,则从集合中取出事件进行发送。

EventBus中设计模式

  • 单例模式:为了避免频繁创建销毁EventBus实例所带来的开销,这里采用DCL的形似来创建单例。
  • 建造者模式:基本上开源库都有很多参数可供用户配置,所以用建造者模式来创建EventBus实例就很合理。

为什么要使用 EventBus 来替代广播呢?

广播:广播是重量级的,消耗资源较多的方式(耗时)。如果不做处理也是不安全的(容易被捕获)。
事件总线:更节省资源、更高效,能将信息传递给原生以外的各种对象。

说下 5 种线程模式的区别

  • POSTING:默认模式,在哪个线程发送事件,就在哪个线程执行订阅方法。
  • MAIN:如果在主线程发送事件,则在主线程执行订阅方法;否则先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。
  • MAIN_ORDERED:无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过 Handler 切换到主线程再执行。
  • BACKGROUND:如果在子线程发送事件,则在子线程执行订阅方法,否则先将事件加入到队列中,然后通过线程池去执行。
  • ASYNC:无论在哪个线程发送事件,都会先将事件加入到队列中,然后通过线程池去执行。

EventBus 是如何做到发送粘性消息的

发送粘性事件的的时候,首先会将事件保存到 stickyEvents 集合,等到注册的时候判断如果是粘性事件,则从集合中取出事件再进行发送。

EventBus2.x的版本和3.x区别

  • 2.x使用的是运行时注解,采用了反射的方式对整个注册的类的所有方法进行扫描来完成注册,因而会对性能有一定影响;

  • 3.x使用的是编译时注解,Java文件会编译成.class文件,再对class文件进行打包等一系列处理。在编译成.class文件时,EventBus会使用EventBusAnnotationProcessor注解处理器读取@Subscribe()注解并解析、处理其中的信息,然后生成Java类来保存所有订阅者的订阅信息。这样就创建出了对文件或类的索引关系,并将其编入到apk中;

  • 从EventBus3.0开始使用了对象池缓存减少了创建对象的开销;

RxBus 与 EventBus 比较

RxBus不是一个库,而是一个文件,实现只有短短30行代码。RxBus本身不需要过多分析,它的强大完全来自于它基于的RxJava技术。所以RxBus的优点其实也就是rxJava的优点:

  • RxJava的Observable有onError、onComplete等状态回调;
  • RxJava使用组合而非嵌套的方式,避免了回调地狱;
  • RxJava的线程调度设计的更加优秀,更简单易用;
  • RxJava可使用多种操作符来进行链式调用来实现复杂的逻辑;
  • RxJava的信息效率高于EventBus2.x,低于EventBus3.x;

那么技术选型时如何取舍呢?
如果项目中使用了RxJava,则使用RxBus,否则使用EventBus3.x;

为什么会有LiveDataBus呢

LiveDataBus是基于LiveData实现的类似EventBus的消息通信框架,它是基于LiveData实现的,在EventBus的基础上加入了生命周期感知,完全可以代替EventBus,RxBus;

常规消息传递优缺点对比:

  • Handler : 容易导致内存泄漏,空指针,高耦合,不利于维护
  • EventBus :原理实现复杂,无法混淆,需要手动绑定生命周期
  • RxBus:依赖于RxJava,包太大,影响apk大小,app启动时间

解更多LiveDataBus可以参考官网:
https://github.com/JeremyLiao/LiveEventBus

implementation 'com.jeremyliao:live-event-bus-x:1.4.5'

常用消息总线考量

LiveEventBus官网
其实目前常用的各种事件总线xxBus原理都差不多,那么在项目中如何使用这些事件总线呢:

  • EventBus,RxBus: 将xxEvent消息容器和事件总线框架的依赖放到base module,其他模块组件依赖于base module; 但是这样每个模块改动都需要增删改baseModule中的消息容器, 组件化要求功能模块独立, 各组件应该尽量避免影响base module;
  • LiveDataBus: 无需建立消息模型,但无法想前两者一样拥有类名索引,无法引导正确的编写代码,也无法传递自定义实体到其他模块;
  • 使用EventBus,RxBus,为了更大程度的解耦,可以独立出一个事件总线module,添加事件的实体都在这个module中,base module依赖 这个事件总线module对事件通信的解耦, 抽离事件到事件总线module中减少对base module的影响;

总结

作者能想到利用反射来查找订阅方法,真的太妙了。同时,随着库的升级,从一开始规定死订阅方法名,到使用注解来指定订阅参数,变得更加灵活好用,再进一步考虑效率,新增缓存以及对象池,推出 subscriber index,一直在改进,真的太棒了。

致谢

探索Android开源框架 - 5. EventBus使用及源码解析
Android 主流开源框架(八)EventBus 源码解析
EventBus 源码解析(很细 很长)
-Android开源框架源码鉴赏:EventBus

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/461528.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

进程、进程组、会话期

进程 在内核中&#xff0c;每个进程都使用一个不同的大于零的正整数来标识&#xff0c;称为进程号pid&#xff08;process ID&#xff09;。 进程组 一个进程可以通过 fork() 调用创建一个或多个子进程&#xff0c;这些进程就可以构成一个进程组。例如&#xff0c; liyongj…

UE4架构初识(四)

目录 UE4仿真引擎学习 一、架构基础 1. GameMode 2. GameState 3. GameSession UE4仿真引擎学习 一、架构基础 1. GameMode 即使最开放的游戏也拥有基础规则&#xff0c;而这些规则构成了 Game Mode。在最基础的层面上&#xff0c;这些规则包括&#xff1a; 出现的玩家和…

深度赋能产业数字化转型,蚂蚁集团数字化三件套亮相中国国际金融展

“十四五”规划纲要指出&#xff1a;加快推动数字产业化&#xff0c;推进产业数字化转型&#xff0c;实施“上云用数赋智”行动&#xff0c;推动数据赋能全产业链协同转型。明确提出了通过科技创新&#xff0c;加快产业数字化转型的要求。 4月25日&#xff0c;以“荟萃金融科技…

Flowable打印调用原生API查询接口的SQL日志

一.简介 建议在 Spring Boot 的 application.properties 中添加如下配置&#xff0c;开启 flowable 日志&#xff1a; logging.level.org.flowabledebug这个配置表示开启 flowable 的日志&#xff0c;开启日志的好处是可以看到底层的 SQL语句。 二.查询部署信息 例如查询流…

【python中的魔法方法有哪些?】

__init__(self, ...): 类的构造函数&#xff0c;用于创建一个类的实例并初始化它的属性。__str__(self): 返回对象的字符串表示形式&#xff0c;可以用于打印对象或者转化成字符串。__repr__(self): 返回对象的字符串表示形式&#xff0c;通常是用于开发者调试和查看对象信息。…

4.24~25(总结)

第一周任务 - Virtual Judge 分析&#xff1a;这道题开始想错了&#xff0c;所以错了一次。后来又仔细读了一遍题&#xff0c;才发现&#xff0c;要是最长的那个排序子数组&#xff0c;所以第二次就做出来了&#xff0c;它其实应该分为两大块&#xff0c;第一块找左边的起点&a…

HTTPS (HTTP+SSL) 对称/非对称加密 中间人攻击 证书加密

&#x1f496; 欢迎来阅读子豪的博客&#xff08;JavaEE篇 &#x1f934;&#xff09; &#x1f449; 有宝贵的意见或建议可以在留言区留言 &#x1f4bb; 欢迎 素质三连 点赞 关注 收藏 &#x1f9d1;‍&#x1f680;码云仓库&#xff1a;补集王子的代码仓库 不要偷走我小火…

“源擎”云原生分布式核心业务系统有什么产品优势?

“源擎”核心系统利用云原生、分布式、微服务技术&#xff0c;基于企业架构设计思想&#xff0c;构建了基础服务、业务服务、交易中心以及系列支撑组件&#xff0c;包含业务架构和多个微服务应用。 业务架构中&#xff0c;交易中心为银行提供了更灵活的选择&#xff0c;支持产…

出现Invalid bound statement (not found)问题的解决办法(已解决)

前言&#xff1a; 今天在写项目时出现了Invalid bound statement (not found):xxxx这个问题&#xff0c;网上找了很多博客都不行&#xff0c;最后修改了配置文件解决了问题&#xff0c;借此将此类问题常见的解决办法汇总一下。 话不多说&#xff0c;直接列出解决办法如下&…

linux-0.11 研究

前言 多阅读优秀代码&#xff0c;才能提高快速、深刻理解代码的能力。linux 内核源码无疑是最好的选择&#xff0c;不过当前 linux 内核版本为 6.3&#xff0c;代码量是相当庞大了&#xff0c;别说看完整个代码了&#xff0c;就算是看完一个子系统&#xff0c;都是不太现实的。…

PostgreSQL的安装与配置(Windows版本)

下载windows安装包 官网地址 安装 官网下载安装包 安装 测试 默认信息直接回车&#xff0c;最后密码输入设置的超级用户密码即可

【Python游戏】在这款程序员游戏新作《现代空战—战机游戏》里,你可以体验一把紧张的空战感觉、刺激鸭~打飞机游戏都能有那么多骚操作……

导语 不知道大家有没有幻想过遨游在广阔蓝天进行惊险的空战吗? 所有文章完整的素材源码都在&#x1f447;&#x1f447; 粉丝白嫖源码福利&#xff0c;请移步至CSDN社区或文末公众hao即可免费。 虽然这样的画面常常只会出现在电影之中&#xff0c;但小编今天给大家编写的一款…

【数据结构】顺序表和链表基本实现(含全代码)

文章目录 一、什么是线性表1. 什么是顺序表动态开辟空间和数组的问题解释LeetCode-exercise 2. 什么是链表2.1链表的分类2.2常用的链表结构及区别2.3无头单向非循环链表的实现2.4带头双向循环链表的实现2.5循序表和链表的区别LeetCode-exercise 3. 快慢指针LeetCode-exercise 一…

【牛客刷题专栏】0x24:JZ23 链表中环的入口结点(C语言编程题)

前言 个人推荐在牛客网刷题(点击可以跳转)&#xff0c;它登陆后会保存刷题记录进度&#xff0c;重新登录时写过的题目代码不会丢失。个人刷题练习系列专栏&#xff1a;个人CSDN牛客刷题专栏。 题目来自&#xff1a;牛客/题库 / 在线编程 / 剑指offer&#xff1a; 目录 前言问…

【GPT】文本生成任务(生成摘要、文本纠错、机器翻译等的模型微调)

note 文章目录 note一、NLG任务二、NLG之文本摘要2.1 基于mT5的文本摘要2.2 基于openai接口测试2.3 基于chatGPT接口 三、根据自己的数据集进行模型微调四、文本纠错任务五、机器翻译任务Reference 一、NLG任务 NLG&#xff1a;自然语言生成任务&#xff0c;很多NLP任务可以被…

Redis入门到入土(day02)

五大数据类型 官方文档 全段翻译&#xff1a; Redis是一个开放源代码&#xff08;BSD许可&#xff09;的内存中数据结构存储&#xff0c;用作数据库&#xff0c;缓存和消息代理。它支持数据结构&#xff0c;例如字符串&#xff0c;哈希&#xff0c;列表&#xff0c;集合&#…

vue项目 解决el-table自适应高度,vue页面不显示多条滚动条,超出的部分让el-table内部出现滚动条(推荐使用第二种解决方案)

一、需求 后台管理系统&#xff1a;最常见的页面都是由—>左侧菜单、头部tabView页签、主体数据渲染页面&#xff08;AppMain&#xff09;&#xff1b;而一般AppMain页面又分为&#xff1a; 搜索区域、table数据&#xff08;分页&#xff09;&#xff0c;可能也会存在底部&a…

Reid训练代码之数据集处理

本篇文章是对yolov5_reid这篇文章训练部分的详解。 该项目目录为&#xff1a; . |-- config # reid输入大小&#xff0c;数据集名称&#xff0c;损失函数等配置 |-- configs # 训练时期超参数定义 |-- data # 存储数据集和数据处理等代码&#xff0c;以及yolov5类别名称等 |--…

【高分论文密码】大尺度空间模拟预测与数字制图技术

大尺度空间模拟预测和数字制图技术和不确定性分析广泛应用于高分SCI论文之中&#xff0c;号称高分论文密码。 大尺度模拟技术可以从不同时空尺度阐明农业生态环境领域的内在机理和时空变化规律&#xff0c;又可以为复杂的机理过程模型大尺度模拟提供技术基础。 在本次&#x…

cocosLua 之 RichText(1)

结构 富文本主要通过RichText来实现, 其继承结构&#xff1a; #mermaid-svg-AHbMrHe3zp3q1wTZ {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-AHbMrHe3zp3q1wTZ .error-icon{fill:#552222;}#mermaid-svg-AHbMrHe3z…