一篇文章搞定《EventBus详解》

news2024/11/18 6:48:14

一篇文章搞定《EventBus详解》

  • 前言
  • EventBus简述
  • EventBus的使用
  • EventBus源码解析
    • 初始化并构建实例
      • EventBus.getDefault()
      • EventBus.builder()
      • EventBus初始化的重要成员
    • 注册流程
      • register方法
      • SubscriberMethodFinder类
      • findSubscriberMethods方法
      • findUsingReflection方法(反射查找)
      • findUsingReflectionInSingleClass方法(反射查找)
      • findUsingInfo方法(索引查找)
      • checkAdd方法(二级检测)
      • subscribe方法
      • subscriberMethod.eventType
      • 小结
    • 普通发送
      • post方法
      • postSingleEvent方法
      • postSingleEventForEventType方法
      • postToSubscription方法
      • invokeSubscriber方法
    • 发布器
      • mainThreadPoster
      • AsyncPoster
      • backgroundPoster
    • 粘性事件
      • 注册时的粘性事件处理
      • checkPostStickyEventToSubscription方法
      • 主动发布黏性事件
      • 移除粘性事件
    • 注销流程
    • 搞两个问题
      • 继承的坑
      • EventBus是如何进行线程间切换的?
  • 总结

前言

最近几篇文章会对常见的三方库有一个清晰的解析。
包含使用场景、基本使用、源码解析、常见问题等等几个方面。
希望在源码解析的过程能学习到,三方库设计的理念和思想。
本篇文章也是请到了EventBus来做客
会以EventBus的v3.3.1版本对此进行解析。

补:做好准备,看源码就是很崩溃的(都快给我写崩溃了)。但是源码的重要性就不用多BB了。

EventBus简述

本篇文章是以,解析源码为主。简单借用官方来简述一下什么是EventBus。
官方解释:EventBus能够简化各组件间的通信,让我们的代码书写变得简单,能有效的分离事件发送方和接收方(也就是解耦的意思),能避免复杂和容易出错的依赖性和生命周期问题。
我的理解:能替换现在的组件通信,比如臃肿的Handler,从而将代码变简单,还能解决一些生命周期等Bug。

EventBus的使用

一、引入EventBus库
首先,在项目的build.gradle文件中添加以下依赖:

implementation 'org.greenrobot:eventbus:3.3.1'

二、定义事件类
EventBus基于事件驱动机制,我们需要定义事件类来传递数据。事件类可以是POJO(Plain Old Java Object)类,表示一个具体的事件。例如:

public class MessageEvent {
    public String message;

    public MessageEvent(String message) {
        this.message = message;
    }
}

三、注册订阅者
在需要接收事件的Activity或Fragment的生命周期中,我们需要注册和取消注册订阅者。可以在onStart()方法中注册,onStop()方法中取消注册。示例如下:

@Override
protected void onStart() {
    super.onStart();

    // 注册订阅者
    EventBus.getDefault().register(this);
}

@Override
protected void onStop() {
    super.onStop();

    // 取消注册订阅者
    EventBus.getDefault().unregister(this);
}

四、定义事件处理方法
订阅者通过定义事件处理方法来处理接收到的事件。事件处理方法需要加上@Subscribe注解,并且只能有一个参数,该参数表示接收到的事件对象。示例如下:

@Subscribe
public void onMessageEvent(MessageEvent event) {
    // 处理接收到的事件
    String message = event.message;
    // TODO: 处理接收到的数据
}

五、发布事件
在需要发布事件的地方,我们可以调用EventBus的post()方法来发布事件。示例如下:

EventBus.getDefault().post(new MessageEvent("Hello, EventBus!"));

六、接收事件
当有事件被发布时,EventBus会自动调用订阅者的事件处理方法。订阅者可以在事件处理方法中接收到发布的事件,并进行相应的处理。

七、处理事件线程模式
事件处理方法可以通过@Subscribe注解指定不同的线程模式,包括ThreadMode.MAIN表示在主线程中执行,ThreadMode.BACKGROUND表示在后台线程中执行,ThreadMode.ASYNC表示在单独的线程中执行等。根据业务需求选择合适的线程模式。

EventBus源码解析

那就按照我们使用的顺序去讲解我们的EventBus源码吧

初始化并构建实例

可以看到EventBus提供给我们的两种构建的方式。

  • EventBus.getDefault()
  • EventBus.builder()
    在这里插入图片描述

EventBus.getDefault()

我们常使用的是 EventBus.getDefault() 该方法获取一个实例来进行发布事件,我们看下源码部分:
EventBus.java

public class EventBus {
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    private final Map<Class<?>, Object> stickyEvents;
    
    // 默认实例变量
    static volatile EventBus defaultInstance;
    
    // 默认的 EventBus 构建器
    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    
    // 双重校验锁式单例
    public static EventBus getDefault() {
        EventBus instance = defaultInstance;
        if (instance == null) {
            synchronized (EventBus.class) {
                instance = EventBus.defaultInstance;
                if (instance == null) {
                    instance = EventBus.defaultInstance = new EventBus();
                }
            }
        }
        return instance;
    }

    // 无参构造
    public EventBus() {
        this(DEFAULT_BUILDER);
    }
    
    // 可见的一个形参的构造
    EventBus(EventBusBuilder builder) {
        logger = builder.getLogger();
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadSupport = builder.getMainThreadSupport();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        ........
    }   
}

通过代码我们可以看到,整体的 getDefault() 方法其实就是一个双重校验锁式单例的写法,其中创建实例的代码是调用了公开的无参构造,而这个无参构造最后调用了一个参数的构造 EventBus(EventBusBuilder builder) 传入了一个默认构建器。

可以看到其实getDefault()最后还是调用了EventBus(EventBusBuilder builder)的构造函数。还是builder的方式。只不过getDefault()只是帮我们进行了默认配置。

EventBus.builder()

这种构建者方式提供一个给我们可以自己构建的EventBus对象。通过.build()我们可以对EventBus进行配置。那么我们都可以配置那些内容呢?
上图中可以看到可以链式Builder的内容,下面让我们看一下EventBusBuilder的源码来了解一下:
EventBusBuilder.java

@SuppressWarnings("unused")
public class EventBusBuilder {
    .....
    //构造函数
    EventBusBuilder() { }

    /** 配置订阅函数执行有异常时,是否打印异常信息   默认:true*/
    public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
        this.logSubscriberExceptions = logSubscriberExceptions;
        return this;
    }

    /**
     * 配置订阅函数执行有异常时,是否发布 SubscriberExceptionEvent 事件
     * 默认:true
     */
    public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {...}

    /**
     * 配置事件无匹配订阅函数时,是否发布 NoSubscriberEvent
     * 默认:true
     */
    public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {...}

    /**
     * 如果订阅者方法执行有异常时,是否抛出 SubscriberException
     * 默认:false
     */
    public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {...}

    /**
     * 配置是否事件继承
     */
    public EventBusBuilder eventInheritance(boolean eventInheritance) {...}

    /**
     * 为 EventBus 提供一个自定义线程池,用于异步和后台事件传递。
     * 这是一个可以破坏事情的高级设置:确保给定的 ExecutorService 不会卡住以避免未定义的行为。
     */
    public EventBusBuilder executorService(ExecutorService executorService) {...}

    /**
     * 跳过类的方法验证
     */
    public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {...}

    /**
     * 配置是否即使有生成的索引也强制使用反射
     */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {...}

    /**
     * 是否启用严格的方法验证
     */
    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {...}

    /**
     * 添加索引类
     */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {...}

    /**
     * 为所有 EventBus 日志设置特定的日志处理程序
     * 默认情况下,所有日志记录都是通过 Android 上的 {@code android.util.Log} 或 JVM 上的 System.out。
     */
    public EventBusBuilder logger(Logger logger) {...}

    /**
     * 获取 Logger
     */
    Logger getLogger() {...}

    /**
     * 获取主线程支持
     */
    MainThreadSupport getMainThreadSupport() {...}

    /**
     * 使用此构建器的值安装由 {@link EventBus#getDefault()} 返回的默认 EventBus
     * 在第一次使用默认 EventBus 之前必须只执行一次
     * 此方法调用后,再通过 {@link EventBus#getDefault()} 获取到的对象就是该构建器配置的实例
     * 所以该方法应该在最后调用
     */
    public EventBus installDefaultEventBus() {...}

    /**
     * 根据当前配置构建 EventBus
     */
    public EventBus build() {
        return new EventBus(this);
    }
}

EventBus初始化的重要成员

可以看到在EventBus(EventBusBuilder builder) 中初始化了一些成员(只保留了重要的几个)

private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<>();
private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
private final Map<Object, List<Class<?>>> typesBySubscriber;
private final Map<Class<?>, Object> stickyEvents;
    
EventBus(EventBusBuilder builder) {
        subscriptionsByEventType = new HashMap<>();
        typesBySubscriber = new HashMap<>();
        stickyEvents = new ConcurrentHashMap<>();
        mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
        ........
}   

这些初始化的成员是非常重要的,掌管着我们的整个EventBus的注册监听和发送的流程。让我们挨个来看看。

  • Map<Class<?>, CopyOnWriteArrayList> subscriptionsByEventType

存储所有的订阅者方法,以订阅者方法接收的事件类型进行分类。 key: 事件类的 Class 对象,value: 订阅者方法包装类集合。

  • Map<Object, List<Class<?>>> typesBySubscriber

用于存储订阅者接收的事件类型。key: 订阅者实例,value: 所接收的事件类型的 Class 对象 List。

  • Map<Class<?>, Object> stickyEvents

用于存储当前存在的最新黏性事件。key: 事件类的 Class 对象,value: 当前最新的黏性事件。是ConcurrentHashMap的

  • Map<Class, List>> eventTypesCache = new HashMap<>()

用于存储事件类型的所有父级类型,包括超类和接口。key: 事件 Class 对象,value: 事件 Class 对象的所有父级 Class 对象,包括超类和接口

  • mainThreadPoster

用于在主线程中发布和处理事件。它通过mainThreadSupport.createPoster(this)创建,当主线程支持(mainThreadSupport)不为null时,才会创建该对象;否则为null。

  • backgroundPoster

用于在后台线程中发布和处理事件。它负责将事件发送到订阅者方法的背景线程进行处理。该成员变量是BackgroundPoster类的实例。

  • asyncPoster

用于在异步线程中发布和处理事件。它负责将事件发送到订阅者方法的异步线程进行处理。该成员变量是AsyncPoster类的实例。

  • subscriberMethodFinder

订阅方法查找器:用于帮助我们找到,注册了EventBus的类中的订阅方法。并放入到订阅集合中。
下面让我们来看看这些成员变量在流程中发挥的作用吧

注册流程

代码中我们注册都是通过在onStart时通过EventBus.getDefault().register(this)进行注册。

@Override
protected void onStart() {
    super.onStart();

    // 注册订阅者
    EventBus.getDefault().register(this);
}

那么注册之后发生了什么呢?

register方法

可以看一下register的源码

public void register(Object subscriber) {

    //第一个If判断主要是判断是否是Android平台并且导入了Android的分支包,因为EventBus还支持Java平台
    //从 3.3.0 版本开始 greenrobot 将 EventBus 分离为 Java 和 Android 两部分,并且默认情况下引入的依赖为 Android 版本,Java 版本需要调整依赖地址。
    if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
        // Crash if the user (developer) has not imported the Android compatibility library.
        throw new RuntimeException("It looks like you are using EventBus on Android, " +
                "make sure to add the \"eventbus\" Android library to your dependencies.");
    }

    // 1、反射获取订阅者的 Class 对象
    Class<?> subscriberClass = subscriber.getClass();
    // 2、通过 subscriberMethodFinder 订阅方法查找器去查找该Class的订阅者的订阅方法,
    //得到一个订阅方法List List<SubscriberMethod>
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // 3、加同步锁,监视器为当前 EventBus 对象
    synchronized (this) {
        // 4、对订阅方法 List 进行遍历
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
             // 5、遍历到的每一个方法对其产生订阅关系,就是正式存放在订阅者的大集合中
            subscribe(subscriber, subscriberMethod);
        }
    }
}

0、首先对 Android 平台进行了检测
1、反射获取订阅者的 Class 对象:也就是我们进行EventBus.getDefault().register(this)的类
2、通过 SubscriberMethodFinder(订阅方法查找器)调用 findSubscriberMethods(Class<?> subscriberClass) 方法来对当前订阅者实例进行订阅方法的查找
3、拿到所有的订阅方法后对结果遍历调用 subscribe(Object subscriber, SubscriberMethod subscriberMethod) 将方法存放于总的事件集合中。

总结:该方法首先对平台进行判断、查找订阅方法、放入总集合便于后面发布事件使用。接下来进一步讲解其中的如何找到订阅方法。

SubscriberMethodFinder类

该类就是查找订阅方法的。还是比较重要的。后面的查找订阅的核心方法都在这个类中,让我们先来解析一下这个类:

class SubscriberMethodFinder {
    private static final int BRIDGE = 0x40;
    private static final int SYNTHETIC = 0x1000;
    private static final int MODIFIERS_IGNORE = Modifier.ABSTRACT | Modifier.STATIC | BRIDGE | SYNTHETIC;
    /**
     * 订阅者方法缓存 ConcurrentHashMap,为了避免重复查找订阅者的订阅方法,维护了此缓存
     * key:   Class<?>                 订阅者 Class 对象
     * value: List<SubscriberMethod>>  订阅者方法 List
     */
    private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
    // 订阅者索引类集合
    private List<SubscriberInfoIndex> subscriberInfoIndexes;
    // 是否进行严格的方法验证 默认值为 false
    private final boolean strictMethodVerification;
    // 是否忽略生成的索引 默认值为 false
    private final boolean ignoreGeneratedIndex;
    // FIND_STATE_POOL 长度
    private static final int POOL_SIZE = 4;
    // FindState 池,默认4个位置 POOL_SIZE = 4
    private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];

    SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification, boolean ignoreGeneratedIndex) {...}

    /**
     * 查找订阅者方法
     * @param subscriberClass Class<?> 订阅者 Class 对象
     * @return List<SubscriberMethod> 订阅者方法List
     */
    List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {...}

    /**
     * 查找订阅者方法
     * @param subscriberClass Class<?> 订阅者 Class 对象
     * @return List<SubscriberMethod> 订阅者方法 List
     */
    private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {...}

    /**
     * 从 FindState 中获取订阅者方法并正式发布
     * 该方法其实只是将形参 findState 中的 subscriberMethods 以新的 List 返回出来
     * 并且还对 findState 做了资源释放及回收的处理
     * @param findState FindState
     * @return List<SubscriberMethod> 订阅者方法
     */
    private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {...}

    /**
     * 准备 FindState,会从对象池中去获取,没有缓存的情况下会去 new 一个新对象
     * 此处运用了对象复用及池化技术
     * @return FindState
     */
    private FindState prepareFindState() {...}

    /**
     * 获取索引类
     * @param findState FindState 查找状态类
     * @return SubscriberInfo 索引类
     */
    private SubscriberInfo getSubscriberInfo(FindState findState) {...}

    /**
     * 通过反射查找订阅者方法
     * @param subscriberClass Class<?> 订阅者 Class 对象
     * @return List<SubscriberMethod> 订阅者方法 List
     */
    private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {...}

    /**
     * 通过反射查找 {@link FindState#clazz} 对应类的订阅者方法
     * @param findState FindState
     */
    private void findUsingReflectionInSingleClass(FindState findState) {...}

    /**
     * 查找状态
     * 主要就是在查找订阅者方法的过程中记录一些状态信息
     * FindState 类是 SubscriberMethodFinder 的内部类,这个方法主要做一个初始化的工作。
     * 由于该类中字段多,为了内存做了对象缓存池处理,见{@link #FIND_STATE_POOL}
     */
    static class FindState {
        // 订阅者方法 List
        final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
        // 按事件类型区分的方法 HashMap
        final Map<Class, Object> anyMethodByEventType = new HashMap<>();
        // 按方法签名存储
        final Map<String, Class> subscriberClassByMethodKey = new HashMap<>();
        // StringBuilder
        final StringBuilder methodKeyBuilder = new StringBuilder(128);
        // 订阅者的 Class 对象
        Class<?> subscriberClass;
        // 当前类的 Class 对象,该字段会随着从子类到父类查找的过程而进行赋值当前类的 Class 对象
        Class<?> clazz;
        // 是否跳过父类的方法查找
        boolean skipSuperClasses;
        // 索引类 初始值为 null
        SubscriberInfo subscriberInfo;

        /**
         * 为订阅者进行初始化
         * @param subscriberClass Class<?> 订阅者 Class 对象
         */
        void initForSubscriber(Class<?> subscriberClass) {...}

        /**
         * 释放资源,准备下一次复用
         */
        void recycle() {...}

        /**
         * 检查并将方法添加
         * @param method    Method 订阅方法
         * @param eventType Class<?> 事件 Class 对象,也就是该方法的形参
         * @return 校验结果
         */
        boolean checkAdd(Method method, Class<?> eventType) {...}

        /**
         * 检查并将方法添加,对方法签名校验
         * @param method    Method 订阅方法
         * @param eventType Class<?> 事件 Class 对象,也就是该方法的形参
         * @return 校验结果
         */
        private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {...}

        /**
         * 移动到父类
         */
        void moveToSuperclass() {...}
    }

这里需要说一下FindState这个内部类。他是记录我们查找过程的一个类。因为我们查找的过程,不仅会遍历父类,还记录了遍历过的类的所有方法信息。

findSubscriberMethods方法

首先subscriberMethodFinder是订阅方法查找器,也是在EventBus(EventBusBuilder builder) 中初始化的。
我们来具体解析一下findSubscriberMethods方法。
subscriberMethodFinder.findSubscriberMethods():

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // 1、首先尝试从缓存中获取订阅方法 List
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    //2、判断从缓存中获取订阅方法 List 是否为null
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    // 3、是否忽略生成的索引(默认是False的)
    if (ignoreGeneratedIndex) {
        // 4、忽略索引的情况下,通过反射进行查找订阅者方法
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        // 4、通过索引的方式进行查找 (更高效)
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    
    // 5、如果没有订阅者方法,就抛出 EventBusException 异常
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
         //6、 将此订阅者和其订阅者方法添加进缓存中 (下次不就有缓存了吗)
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        //7、返回结果List
        return subscriberMethods;
    }
}

1、首先是先从缓存中尝试获取该订阅者的订阅方法
2、后续代码对 ignoreGeneratedIndex 进行了判断,该属性可以通过EventBusBuilder.ignoreGeneratedIndex() 进行配置的。
这个默认是false也就是默认是索引查找的(优点在下面的findUsingInfo索引查找 会说到)

/** Forces the use of reflection even if there's a generated index (default: false). */
public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
    this.ignoreGeneratedIndex = ignoreGeneratedIndex;
    return this;
}

我们可以手动将他置为True去走反射查找。

EventBus.builder().ignoreGeneratedIndex(true)

3、当忽略索引类时,通过反射 findUsingReflection(Class<?> subscriberClass) 进行查找,如果不忽略索引,使用 findUsingInfo(Class<?> subscriberClass) 方法进行查找。
4、查找完成后,将结果存入 METHOD_CACHE 缓存中以便下次再对此订阅者进行查找时直接从缓存读取数据
5、返回结果

findUsingReflection方法(反射查找)

既然是将源码,就别怕源码粘贴的多。也别怕源码看得多。

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    //1、获取一个 FindState 对象记录中间过程状态
    FindState findState = prepareFindState();
    //2、 为当前订阅者初始化 FindState
    findState.initForSubscriber(subscriberClass);
    //3、 循环 从子类到父类通过findUsingReflectionInSingleClass进行反射查找订阅方法
    while (findState.clazz != null) {
        //根据使用findState.clazz 再通过反射查找订阅方法
        findUsingReflectionInSingleClass(findState);
        // 查找阶段结束,移动到当前类的父类
        findState.moveToSuperclass();
    }
    //4、 返回查找到的订阅者方法并释放资源
    return getMethodsAndRelease(findState);
}

findUsingReflection中主要是初始化了FindState状态并驱动findUsingReflectionInSingleClass去获取订阅方法。最后遍历子类和父类。

findUsingReflectionInSingleClass方法(反射查找)

我称他为真正干活的大哥,通过反射获取订阅方法。
方法源码过长,如果不想看,就别放弃本篇文章。我可以对这个方法进行简述总结:
1、通过反射获取当前 Class 的方法(这里需要注意getMethods和getDeclaredMethods)

  • getMethods(): 该方法是获取本类以及父类或者父接口中所有的公共方法( public 修饰符修饰的)
  • getDeclaredMethods(): 该方法是获取本类中的所有方法,包括私有的( private、protected、默认以及 public )的方法。这比 getMethods() 快,尤其是当订阅者是像 Activity 这样的巨大的类时

2、取到方法的信息。

  • 修饰符:订阅者方法有一定的限制,必须为 public,不能是static volatile abstract strict
  • 参数列表: 参数个数为1的情况下,符合订阅者方法对于参数个数的限制
  • 注解:获取方法的注解,判断是否存在 Subscribe
  • 线程模型:包装SubscriberMethod的给后面分发用

总结:是不是,看不进去源码也别放弃,大哥帮你解析总结。findUsingReflectionInSingleClass这不就是纯纯干活的吗。

private void findUsingReflectionInSingleClass(FindState findState) {
    // 声明一个 方法数组
    Method[] methods;
    try {
        // 通过反射获取当前 Class 的方法,某些 Android 版本在调用 getDeclaredMethods 或 getMethods 时似乎存在反射错误
        // getMethods(): 该方法是获取本类以及父类或者父接口中所有的公共方法(public修饰符修饰的)。
        // getDeclaredMethods(): 该方法是获取本类中的所有方法,包括私有的(private、protected、默认以及public)的方法。
        // getDeclaredMethods() 这比 getMethods() 快,尤其是当订阅者是像活动这样的巨大的类时
        methods = findState.clazz.getDeclaredMethods();
    } catch (Throwable th) {
        // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
        try {
            // 当 getDeclaredMethods() 发生异常时,尝试使用 getMethods()
            methods = findState.clazz.getMethods();
        } catch (LinkageError error) { // super class of NoClassDefFoundError to be a bit more broad...
            // 当 getMethods() 也产生了异常时,会抛出 EventBusException 异常
            String msg = "Could not inspect methods of " + findState.clazz.getName();
            if (ignoreGeneratedIndex) {
                msg += ". Please consider using EventBus annotation processor to avoid reflection.";
            } else {
                msg += ". Please make this class visible to EventBus annotation processor to avoid reflection.";
            }
            throw new EventBusException(msg, error);
        }
        // 跳过父类的方法查找,因为在此 catch 中已经获取了所有父类方法,所以要跳过父类
        findState.skipSuperClasses = true;
    }

    // 代码执行到此处表示反射获取方法没有产生异常
    // 对获取到的类中的所有方法进行遍历操作
    for (Method method : methods) {
        // 获取方法的修饰符
        int modifiers = method.getModifiers();
        // 订阅者方法有一定的限制,必须为 public,不能是static volatile abstract strict
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
        // 获取该方法的参数列表
        Class<?>[] parameterTypes = method.getParameterTypes();

        // 对参数个数进行判断
        if (parameterTypes.length == 1) {
            // 参数个数为1的情况下,符合订阅者方法对于参数个数的限制
            // 获取方法的注解,判断是否存在 Subscribe
            Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
            if (subscribeAnnotation != null) {
                // 如果方法存在 Subscribe 注解
                // 获取方法参数的 Class 对象
                Class<?> eventType = parameterTypes[0];
                // 对订阅者方法进行二级校验
                if (findState.checkAdd(method, eventType)) {
                    // 获取订阅者方法的线程模型
                    ThreadMode threadMode = subscribeAnnotation.threadMode();
                    // 将此订阅者方法 添加进 subscriberMethods
                    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
        // 方法不是 public,进一步判断
        // 判断是否进行严格的方法验证,如果是,再判断当前方法是否有 Subscribe 注解
        // 两个条件都成立的情况下,抛出 EventBusException 异常,该异常来自于严格的方法验证
        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");
            }
    }
}

findUsingInfo方法(索引查找)

findUsingInfo是EventBus中使用的一种优化技术。
首先这个索引是怎么生成的呢?

  • 在初始化过程中,EventBus会遍历所有订阅者类,分析其订阅方法并生成索引数据结构,以提供快速的事件查找。这个过程会解析订阅者类的订阅方法,提取订阅方法的参数信息和事件类型,并将其存储到一个Map数据结构中。

有什么优点?

  • 索引根据事件类型建立,可以快速找到订阅者对应的方法,以提高事件调度的效率。索引查找不需要使用反射,所以相对来说更加高效。

上面说了看不下去源码我会帮你总结的。往下看!!!!!

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    // 获取一个 FindState 对象
    FindState findState = prepareFindState();
    // 为当前订阅者初始化 FindState
    findState.initForSubscriber(subscriberClass);
    // 循环 从子类到父类查找订阅方法
    while (findState.clazz != null) {
        // 获取索引类并赋值给 findState.subscriberInfo
        findState.subscriberInfo = getSubscriberInfo(findState);
        // 当索引类的信息类不为 null 时,进一步操作
        if (findState.subscriberInfo != null) {
            // 获取索引类的所有订阅者方法
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            // 对订阅者方法数组遍历
            for (SubscriberMethod subscriberMethod : array) {
                // 检查并将方法添加
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    // 校验通过,将该订阅方法添加至 subscriberMethods,
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            // 如果没有索引类,就使用反射的方式继续查找
            findUsingReflectionInSingleClass(findState);
        }
        // 将 findState 的 Class 对象移至父类型
        findState.moveToSuperclass();
    }
    // 返回查找到的订阅者方法并释放资源
    return getMethodsAndRelease(findState);
}

该方法前部分和 findUsingReflection(Class<?> subscriberClass) 一致,不一样的地方就是循环体内的逻辑。
那就只说说循环体内都有什么:
1、首先通过getSubscriberInfo获取索引类
2、当索引类的信息类不为 null 时,获取索引类的订阅方法
3、之后遍历校验(和反射的校验一样就不赘述了)
4、当没有拿到索引类时,继续使用findUsingReflectionInSingleClass(FindState findState) 方法反射式查找

checkAdd方法(二级检测)

可以在反射搜索和索引搜索的最后检测中,都能看到checkAdd方法。

findState.checkAdd(method, eventType)

那都check了什么呢?
不愿意看下面的源码:我来告诉你都check到了什么

  1. Java 中的类是具有继承关系的,如果父类声明了 Subscribe 方法,那么就相当于子类也持有了该监听方法,那么子类在 register 后就需要拿到父类的所有 Subscribe 方法
  2. 如果子类继承并重写了父类的 Subscribe 方法,那么子类在 register 后就需要以自己重写后的方法为准,忽略父类的相应方法

这个方法就是来判断这个事的。将准确的监听方法加入到集合。而像上述被忽略的方法过滤掉。

    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) {
                //existing 等于 null 说明之前未解析到监听相同事件的方法,检查通过
                //因为大部分情况下监听者不会声明多个监听相同事件的方法,所以先进行这步检查效率上会比较高
                return true;
            } else { //existing 不等于 null 说明之前已经解析到同样监听这个事件的方法了

                if (existing instanceof Method) {
                    if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                        // Paranoia check
                        throw new IllegalStateException();
                    }
                    // Put any non-Method object to "consume" the existing Method
                    //会执行到这里,说明存在多个方法监听同个 Event,那么将将 eventType 对应的 value 置为 this
                    //避免多次检查,让其直接去执行 checkAddWithMethodSignature 方法
                    anyMethodByEventType.put(eventType, this);
                }
                return checkAddWithMethodSignature(method, eventType);
            }
        }

        private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
            methodKeyBuilder.setLength(0);
            methodKeyBuilder.append(method.getName());
            methodKeyBuilder.append('>').append(eventType.getName());

            //以 methodName>eventTypeName 字符串作为 key
            //通过这个 key 来判断是否存在子类重写了父类方法的情况
            String methodKey = methodKeyBuilder.toString();
            //获取声明了 method 的类对应的 class 对象
            Class<?> methodClass = method.getDeclaringClass();

            Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
            //1. 如果 methodClassOld == null 为 true,说明 method 是第一次解析到,允许添加
            //2. 如果 methodClassOld.isAssignableFrom(methodClass) 为 true
            //2.1、说明 methodClassOld 是 methodClass 的父类,需要以子类重写的方法 method 为准,允许添加
            //     实际上应该不存在这种情况,因为 EventBus 是从子类开始向父类进行遍历的
            //2.2、说明 methodClassOld 是 methodClass 是同个类,即 methodClass 声明了多个方法对同个事件进行监听 ,也允许添加
            if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
                // Only add if not already found in a sub class
                return true;
            } else {
                // Revert the put, old class is further down the class hierarchy
                //由于 EventBus 是从子类向父类进行解析
                //会执行到这里就说明之前已经解析到了相同 key 的方法,对应子类重写了父类方法的情况
                //此时需要以子类重写的方法 method 为准,所以又将 methodClassOld 重新设回去
                subscriberClassByMethodKey.put(methodKey, methodClassOld);
                return false;
            }
        }

subscribe方法

在register方法中
在通过subscriberMethodFinder找到当前class的订阅方法后
最后对方法集合进行遍历调用 subscribe(Object subscriber, SubscriberMethod subscriberMethod)
将订阅方法填入到订阅者的大集合中、并进行订阅者的归类

    // 2、通过 subscriberMethodFinder 订阅方法查找器去查找该Class的订阅者的订阅方法,
    //得到一个订阅方法List List<SubscriberMethod>
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    // 3、加同步锁,监视器为当前 EventBus 对象
    synchronized (this) {
        // 4、对订阅方法 List 进行遍历
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
             // 5、遍历到的每一个方法对其产生订阅关系,就是正式存放在订阅者的大集合中
            subscribe(subscriber, subscriberMethod);
        }
    }

那就看看subscribe都做了些什么呢?
其实我们之前有说的EventBus初始化的HashMap成员变量subscriptionsByEventType。用来存放我们的订阅者们。

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
subscriptionsByEventType = new HashMap<>();
  • 其实subscribe就是把获取的订阅者信息包装成Subscription 对象和相应的Class作为Key加入到这个集合中。
  • 并将这些订阅者信息,按照分类加入到typesBySubscriber集合中。
private final Map<Object, List<Class<?>>> typesBySubscriber;
typesBySubscriber = new HashMap<>();

EventBus.subscribe()

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    // 获取订阅者方法接收的事件类型 Class 对象
    Class<?> eventType = subscriberMethod.eventType;
    // 创建 Subscription
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 从 subscriptionsByEventType 中 尝试获取当前订阅方法接收的事件类型的值
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        // 如果为 null,表示该方法是第一个,创建空的CopyOnWriteArrayList put 进 subscriptionsByEventType
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        // 如果不为 null,判断现有数据中是否存在该方法,如果存在抛出 EventBusException 异常
        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++) {
        // 这一步主要是将订阅者方法添加进 subscriptionsByEventType 数据中,并且会按照优先级进行插入
        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);

    // 对黏性事件进行处理(后面将)
    ...
}

总结一下这个subscribe方法做了什么:
1、先从 subscriptionsByEventType 这个按照事件类型区分的集合中获取当前订阅方法接收的事件类型的值
2、如果为空,表示该方法是第一个,然后创建一个 CopyOnWriteArrayList 将该订阅方法的包装类存入集合中再存入到 subscriptionsByEventType 中;(ps:这就是讲订阅者都放到subscriptionsByEventType大集合中)
3、按照订阅方法的优先级按顺序的插入到原有数据集合中
4、通过subscriberMethod.eventType将订阅者进行归类放入typesBySubscriber集合中
5、既然要分类,看看subscriberMethod.eventType是什么呢?

subscriberMethod.eventType

在findUsingReflectionInSingleClass方法中,通过method.getParameterTypes()获取到了订阅者方法的参数类型数组parameterTypes,其中parameterTypes[0]即为订阅者方法的第一个参数类型,也就是事件类型。这个事件类型可以是任意的Class对象,表示了订阅者方法可以接收该类型的事件。
源码如下:

private void findUsingReflectionInSingleClass(FindState findState) {
    ......
    Class<?>[] parameterTypes = method.getParameterTypes();
    if (parameterTypes.length == 1) {  //方法包含的参数个数是一
       Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
       if (subscribeAnnotation != null) { //方法签名包含 Subscribe 注解
            Class<?> eventType = parameterTypes[0];
    ........

例如,如果一个订阅者方法的定义为

@Subscribe
public void onEvent(MessageEvent event) {
    //处理事件
}

那么subscriberMethod.eventType就是MessageEvent.class。
通过subscriberMethod.eventType可以向EventBus注册该订阅者方法,并在事件发送的时候进行匹配和调用。

小结

OK,注册流程就是这些内容了。内容看起来很多。认真的顺下来就还好。毕竟EventBus作为观察者模式的老大,主要的就是注册和发布。所以这已经是一半的内容了,嘻嘻。
整体流程图如下:
在这里插入图片描述

普通发送

EventBus.getDefault().post(Any)

首先普通发送的post方法中都做了什么呢?

post方法

这个源码还是要看一下的,就算是我能给你总结还是推荐看一下(比较简单)。总结放在源码后了。

//PostingThreadState 主要作用是记录当前线程的事件发布状态、待发布事件等。
//是 EventBus 类的静态内部类
final static class PostingThreadState {
    // 事件队列
    final List<Object> eventQueue = new ArrayList<>();
    // 是否在发布
    boolean isPosting;
    // 是否是主线程
    boolean isMainThread;
    // 订阅者方法包装类
    Subscription subscription;
    // 正在发送的事件
    Object event;
    // 是否已经取消
    boolean canceled;
}

//ThreadLocal 为每个发送消息的线程维护一个 PostingThreadState 对象,
//用于为每个线程维护一个消息队列及其它辅助参数
private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
};

public void post(Object event) {
    // 从当前线程中取得线程专属变量 PostingThreadState 实例
    PostingThreadState postingState = currentPostingThreadState.get();
    // 拿到事件队列
    List<Object> eventQueue = postingState.eventQueue;
    // 将事件入队
    eventQueue.add(event);
    // 判断当前线程是否在发布事件中
    if (!postingState.isPosting) {
        // 设置当前线程是否是主线程
        postingState.isMainThread = isMainThread();
        // 将当前线程标记为正在发布
        postingState.isPosting = true;
        // 如果 canceled 为 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;
        }
    }
}

总的来说就是每次 post 进来的消息都会先存到消息队列 eventQueue中,然后通过 while 循环进行处理,消息处理逻辑是通过 postSingleEvent方法来完成的。所以重点放在postSingleEvent方法中。

postSingleEvent方法

先说一下他是干什么的:(干了两件大事)

  • 第一件事是处理事件的继承关系,找出事件类的所有父类型
  • 第二件事就是按照事件类型进一步执行发布事件的操作

看源码:(总结放后面,其实源码中注视已经很清晰了)

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    // 获取事件的Class对象
    Class<?> eventClass = event.getClass();
    // 订阅者是否找到
    boolean subscriptionFound = false;
    // 判断是否处理事件继承
    if (eventInheritance) {
        // 查找所有事件类型
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        // 循环遍历 所有类型
        int countTypes = eventTypes.size();
        for (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);
        }
        // 判断事件无匹配订阅函数时,是否发布 NoSubscriberEvent
        if (sendNoSubscriberEvent
            && eventClass != NoSubscriberEvent.class
            && eventClass != SubscriberExceptionEvent.class) {
            // 发布 NoSubscriberEvent
            post(new NoSubscriberEvent(this, event));
        }
    }
}
  1. 具有继承关系。此时就需要拿到 EventA 的所有父类型,然后根据 EventA 本身和其父类型关联到的所有监听方法依次进行消息发送
  2. 不具有继承关系。此时只需要向 EventA 的监听方法进行消息发送即可

发送消息就在postSingleEventForEventType方法中喽

postSingleEventForEventType方法

该方法会返回一个 Boolean 结果,表示事件是否发布成功。

  • 方法体内从 subscriptionsByEventType 获取该事件类型的订阅方法,如果不存在订阅方法就返回 false,如果存在订阅方法就进一步处理。
    • subscriptionsByEventType大家还记得吧应该。在强调一下:存储所有的订阅者方法,以订阅者方法接收的事件类型进行分类。
  • 下一步就是对拿到的所有该事件类型的订阅者方法进行遍历操作,将准备发送的事件及事件的订阅者方法赋值给当前线程的 PostingThreadState 对象用于记录。
  • 调用 postToSubscription(Subscription subscription, Object event, boolean isMainThread) 方法进一步处理线程模式进行发布

再看下源码吧:

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    // 加锁 监视器为当前对象
    synchronized (this) {
        // 获取该 Class 对象的订阅方法 List
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        // 如果存在订阅方法,就进行遍历操作
        for (Subscription subscription : subscriptions) {
            // 将事件和订阅方法赋值给 postingState
            postingState.event = event;
            postingState.subscription = subscription;
            // 是否中止
            boolean aborted;
            try {
                // 将事件发布到订阅者
                postToSubscription(subscription, event, postingState.isMainThread);
                // 是否已经取消发布
                aborted = postingState.canceled;
            } finally {
                // 重置 postingState 状态
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            // 如果已经中止,就跳出循环
            if (aborted) {
                break;
            }
        }
        // 方法体结束,返回找到订阅关系
        return true;
    }
    // 到此步骤表示没有订阅方法,返回 false
    return false;
}

postToSubscription方法

在上面的步骤中,最终会调用 postToSubscription来发布事件给订阅者。
这里就需要先说一下他的Mode类型了
目前 EventBus 有五种线程模式,分别是:

模式作用
ThreadMode.POSTING默认设置,事件交付是同步完成的,一旦发布完成,所有订阅者都将被调用。没有线程切换。因此,对于已知无需主线程的非常短的时间完成的简单任务,这是推荐的模式。使用此模式的事件处理程序应该快速返回,以避免阻止发布线程,该线程可能是主线程。
ThreadMode.MAIN在 Android 上, 订阅者将在 Android 的主线程(UI 线程)中调用。如果发布线程是主线程,订阅者方法将被直接调用,阻塞发布线程。否则,事件将排队等待传递(非阻塞)。使用这种模式的订阅者必须快速返回以避免阻塞主线程。
ThreadMode.MAIN_ORDERED在 Android 上,订阅者将在 Android 的主线程(UI 线程)中被调用。与 MAIN 不同,该事件将始终排队等待传递。这确保了 post 调用是非阻塞的。这给了事件处理一个更严格、更一致的顺序
ThreadMode.BACKGROUND在 Android 上,订阅者将在后台线程中调用。如果发布线程不是主线程,订阅者方法将直接在发布线程中调用。如果发布线程是主线程,EventBus 使用单个后台线程,它将按顺序传递其所有事件。使用此模式的订阅者应尽量快速返回以避免阻塞后台线程。
ThreadMode.ASYNC这总是独立于发布线程和主线程。发布事件永远不会等待使用此模式的事件处理程序方法。如果事件处理程序方法的执行可能需要一些时间,例如网络访问,则应使用此模式。避免同时触发大量长期运行的异步处理程序方法,以限制并发线程的数量。

之后来看一下postToSubscription源码:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    // 按照订阅者方法指定的线程模式进行针对性处理
    switch (subscription.subscriberMethod.threadMode) {
        // 发布线程
        case POSTING:
        invokeSubscriber(subscription, event);
        break;
        // Android 上为主线程,非 Android 与 POSTING 一致
        case MAIN:
        // 判断是否是主线程,如果是主线程,直接调用 void invokeSubscriber(Subscription subscription, Object event) 方法进行在当前线程中发布事件
        if (isMainThread) {
            invokeSubscriber(subscription, event);
        } else {
            // 不是主线程,将该事件入队到主线程事件发布器处理
            mainThreadPoster.enqueue(subscription, event);
        }
        break;
        // Android 上为主线程,并且按顺序发布,非 Android 与 POSTING 一致
        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;
        // Android 上为后台线程调用,非 Android 与 POSTING 一致
        case BACKGROUND:
        // 主线程发布的事件才会被入队到 backgroundPoster,非主线程发布的事件会被直接调用订阅者方法发布事件
        if (isMainThread) {
            backgroundPoster.enqueue(subscription, event);
        } else {
            invokeSubscriber(subscription, event);
        }
        break;
        // 使用单独的线程处理,基于线程池
        case ASYNC:
        // 入队 asyncPoster,该线程模式总是在非发布线程处理订阅者方法的调用
        asyncPoster.enqueue(subscription, event);
        break;
        default:
        throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
    }
}

可以看到。源码中通过Mode的类型进行了不同的操作。但是流程是相同的。那我们来看看都有哪些重要的操作和成员。
首先可以看到都会调用invokeSubscriber(subscription, event);
这个方法呢就是最后的调用方法,让我们来先说一下这个方法。

invokeSubscriber方法

直接上源码好不(因为很简单,很短):
其实就是拿到我们的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);
    }
}

到这里还没完呢昂。因为上面还有if判断,不去直接调用invokeSubscriber的情况呢。

发布器

mainThreadPoster、backgroundPoster、asyncPoster
想必记忆里好的朋友们还记的他们三个,是啊就是在初始化EventBusBuilder的时候啊。

mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
backgroundPoster = new BackgroundPoster(this);
asyncPoster = new AsyncPoster(this);

那么他三个干了些什么呢,让我们挨个看一下。
首先他们都实现了 Poster 接口,该接口源码如下:

public interface Poster {

    /**
     * 将要为特定订阅发布的事件排入队列
     * @param subscription Subscription 接收事件的订阅方法包装类
     * @param event        Object 将发布给订阅者的事件
     */
    void enqueue(Subscription subscription, Object event);
}

mainThreadPoster

他就是Poster。只是说不是主线程的话就加入队列就行了。

private final Poster mainThreadPoster;

AsyncPoster

对于 AsyncPoster 来说,其每接收到一个消息,都会直接在 enqueue 方法中将自己(Runnable)提交给线程池进行处理,而使用的线程池默认是 Executors.newCachedThreadPool(),该线程池每接收到一个任务都会马上交由线程进行处理,所以 AsyncPoster 并不保证消息处理的有序性,但在消息处理的及时性方面会比较高,且每次提交给 AsyncPoster 的消息可能都是由不同的线程来处理
源码如下:

class AsyncPoster implements Runnable, Poster {

    // 待发布事件队列
    private final PendingPostQueue queue;
    private final EventBus eventBus;

    AsyncPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    /**
     * 入队
     * @param subscription Subscription 接收事件的订阅者方法包装类
     * @param event        Object 将发布给订阅者的事件
     */
    public void enqueue(Subscription subscription, Object event) {
        // 获取一个 PendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        // 入队
        queue.enqueue(pendingPost);
        // 将任务提交到线程池处理,每个事件都会单独提交,不同于 BackgroundPoster
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        // 获取队头的元素 一一对应,一次任务执行消费一个事件元素
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        // 调用订阅者方法发布事件
        eventBus.invokeSubscriber(pendingPost);
    }
}

backgroundPoster

BackgroundPoster 会将任务依次缓存到 PendingPostQueue 中,每次只取出一个任务交由线程池来执行,所以 BackgroundPoster 会保证消息队列在处理时的有序性,但在消息处理的及时性方面相比 AsyncPoster 要低一些

final class BackgroundPoster implements Runnable, Poster {
    // 待发布事件队列
    private final PendingPostQueue queue;
    private final EventBus eventBus;
    // 执行器运行状态 volatile 可见性保障
    private volatile boolean executorRunning;

    BackgroundPoster(EventBus eventBus) {
        this.eventBus = eventBus;
        queue = new PendingPostQueue();
    }

    public void enqueue(Subscription subscription, Object event) {
        // 获得一个 PendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        // 加锁 监视器为当前对象
        synchronized (this) {
            // 入队
            queue.enqueue(pendingPost);
            // 判断执行器是否在执行
            if (!executorRunning) {
                // 设置状态为正在执行
                executorRunning = true;
                // 向线程池提交任务
                eventBus.getExecutorService().execute(this);
            }
        }
    }

    @Override
    public void run() {
        try {
            try {
                // 循环处理队列中的所有待发布事件
                while (true) {
                    // 取出队头的元素
                    PendingPost pendingPost = queue.poll(1000);
                    // 二次校验
                    if (pendingPost == null) {
                        // 第二级校验是同步的
                        synchronized (this) {
                            pendingPost = queue.poll();
                            if (pendingPost == null) {
                                // 最终没有元素,将执行器置为空闲
                                executorRunning = false;
                                return;
                            }
                        }
                    }
                    // 调用订阅者方法
                    eventBus.invokeSubscriber(pendingPost);
                }
            } catch (InterruptedException e) {
                eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interrupted", e);
            }
        } finally {
            // 执行完后将运行状态置为空闲
            executorRunning = false;
        }
    }
}

而不管是使用什么消息处理策略,最终都是通过调用invokeSubscriber方法来反射调用监听方法

粘性事件

注册时的粘性事件处理

在subscribe中我们可以看到,对粘性方法进行了处理,最后通过checkPostStickyEventToSubscription检测并发布事件。
先不细说,这回知道为什么事件会在register粘过来了吧。
是因为我们在register中会调用subscribe将我们的监听事件放入集合,并同时去处理了粘性事件。处理了~处理了~理了~了。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    ........
    ........
    // 对黏性事件进行处理
    if (subscriberMethod.sticky) {
        // 是否事件继承
        if (eventInheritance) {
            // 必须考虑所有 eventType 子类的现有粘性事件。
            // Note: 迭代所有事件可能会因大量粘性事件而效率低下,因此应更改数据结构以允许更有效的查找
            // (e.g. 存储超类的子类的附加映射: Class -> List<Class>).
            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 {
            // 从黏性事件 Map 中获取当前事件类型的最新事件
            Object stickyEvent = stickyEvents.get(eventType);
            // 校验事件并发布事件
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

首先也是和普通事件一样,进行事件是否处理继承关系的判断,如果不处理,直接从 stickyEvents 获取最新的该事件类型的事件进行校验并发布,因为不一定有已经发布的黏性事件,所以通过 checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) 方法对事件进行校验和发布。

checkPostStickyEventToSubscription方法

直接上源码:源码少的我都直接上,毕竟大家看不了几行。

/**
 * 检查黏性事件并发布到订阅者
 *
 * @param newSubscription Subscription 订阅者方法包装类
 * @param stickyEvent     Object 黏性事件
 */
private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
    if (stickyEvent != null) {
        // 如果订阅者试图中止事件,它将失败(在发布状态下不跟踪事件)
        // --> Strange corner case, which we don't take care of here.
        // 将事件发布到订阅者
        postToSubscription(newSubscription, stickyEvent, isMainThread());
    }
}
  • 如果事件不为空,就调用 postToSubscription(Subscription subscription, Object event, boolean isMainThread) 方法处理线程模式进一步发布
  • postToSubscription。我们可是刚刚讲过的啊。那不就是根据ThreadMode判断完类型最后通过invokeSubscriber反射到监听方法了吗。

总结:是不是有一种自己打自己的感觉,注册的时候发现自己是粘性订阅者,赶紧告诉postToSubscription发布一个跟自己匹配的粘性事件。
那么这个粘性的事件哪里来的呢?

主动发布黏性事件

我可真会引起话题。
主动发布黏性事件是调用的 postSticky(Object event) 方法来完成的。

EventBus.getDefault().postSticky(Any)

方法的源码如下:

/**
 * 将给定事件发布到事件总线并保留该事件(因为它是黏性的).
 * 事件类型的最新粘性事件保存在内存中,供订阅者使用 {@link Subscribe#sticky()} 将来访问。
 */
public void postSticky(Object event) {
    // 加锁 监视器为黏性事件 Map
    synchronized (stickyEvents) {
        // 将事件存入内存中 以事件的 Class 对象为 key,事件实例为 value
        stickyEvents.put(event.getClass(), event);
    }
    // 放置后应发布,以防订阅者想立即删除
    post(event);
}

首先将黏性事件存入 stickyEvents 中,随后调用 post(Object event) 方法进行事件发布,方法源码如下:
为啥存进去? 那还用说!!你看看subscribe的源码,拿出来用呗,拿出来给自己发出来。自己打自己用。

移除粘性事件

移除指定的黏性事件可以通过以下方法来实现,都是用于将指定事件从 stickyEvents 中移除。

public boolean removeStickyEvent(Object event) {
        synchronized (stickyEvents) {
            Class<?> eventType = event.getClass();
            Object existingEvent = stickyEvents.get(eventType);
            if (event.equals(existingEvent)) {
                stickyEvents.remove(eventType);
                return true;
            } else {
                return false;
            }
        }
    }

注销流程

终于到了最后的注销流程了。

  • 为什么要注销?

解除注册的目的是为了避免内存泄露,EventBus 使用了单例模式,如果不主动解除注册的话,EventBus 就会一直持有 subscriber。解除注册是通过 unregister方法来实现的

  • 怎么注销?

源码很少直接上源码

public synchronized void unregister(Object subscriber) {
    // 获取订阅者接收的事件类型
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        // 按事件类型遍历退订相关订阅方法
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        // 从订阅者所接收的事件类型Map中移除该订阅者
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

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

该方法逻辑也比较简单,只是将 subscriber 以及其关联的所有 method 对象从集合中移除而已
而此处虽然会将关于 subscriber 的信息均给移除掉,但是在 SubscriberMethodFinder 中的静态成员变量 METHOD_CACHE 依然会缓存着已经注册过的 subscriber 的信息,这也是为了在某些 subscriber 会先后多次注册 EventBus 时可以做到信息复用,避免多次循环反射。

搞两个问题

继承的坑

问题是:如果子类重写了父类多个 Subscribe 方法的话,就会抛出 IllegalStateException。是因为什么
例如,在下面的例子中。父类 BaseActivity 声明了两个 Subscribe 方法,子类 MainActivity 重写了这两个方法,此时运行后就会抛出 IllegalStateException。而如果 MainActivity 不重写或者只重写一个方法的话,就可以正常运行

open class BaseActivity : AppCompatActivity() {

    @Subscribe
    open fun fun1(msg: String) {

    }

    @Subscribe
    open fun fun2(msg: String) {

    }

}

class MainActivity : BaseActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        EventBus.getDefault().register(this)
    }

    override fun onDestroy() {
        super.onDestroy()
        EventBus.getDefault().unregister(this)
    }

    @Subscribe
    override fun fun1(msg: String) {

    }

    @Subscribe
    override fun fun2(msg: String) {

    }

}

解答:是在 FindState 的 checkAdd 方法抛出了异常
1、EventBus 对 Subscribe 方法的解析方向是子类向父类进行的,同个类下的 Subscribe 方法按照声明顺序进行解析
2、当 checkAdd 方法开始解析 BaseActivity 的 fun2 方法时,existing 对象就是BaseActivity.fun1,此时就会执行到操作 1,而由于子类已经重写了 fun1 方法,此时 checkAddWithMethodSignature 方法就会返回 false,最终导致抛出异常

EventBus是如何进行线程间切换的?

EventBus中通过使用Subscribe注解设置的ThreadMode来执行线程调度,分为以下几种情况:

  1. POSTING 事件订阅者和发布者所在线程一致,不用切换。
  2. MAIN 如果发布者位于主线程则直接回调,否则内部通过Handler来切换到主线程。
  3. MAIN_ORDERED 不区分发布者所在线程,内部通过Handler来切换到主线程。
  4. BACKGROUND 如果发布者在子线程,则直接回调,否则通过EventBus内部线程池切换到子线程执行。
    • 我们可以通过 EventBusBuilder.executorService(ExecutorService executorService) 方法进行自定义线程池,如果没有自定义设置就会使用默认的线程池,默认的线程池在 EventBusBuilder 中创建,代码如下:
private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();
  1. ASYNC 通过EventBus内部线程池切换到子线程执行。
    • Executors.newCachedThreadPool()

这里说一下MAIN和MAIN_ORDERED吧
我们知道这两种是利用mainThreadPoster发送消息。

mainThreadSupport = builder.getMainThreadSupport();
mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;

回到代码中可以看到是通过mainThreadSupport创建,那么看看mainThreadSupport是怎么回事?
mainThreadSupport

public interface MainThreadSupport {

    boolean isMainThread();

    Poster createPoster(EventBus eventBus);
}

实现类DefaultAndroidMainThreadSupport

public class DefaultAndroidMainThreadSupport implements MainThreadSupport {

    @Override
    public boolean isMainThread() {
        return Looper.getMainLooper() == Looper.myLooper();
    }

    @Override
    public Poster createPoster(EventBus eventBus) {
        return new HandlerPoster(eventBus, Looper.getMainLooper(), 10);
    }
}

主线程切换类HandlerPoster

public class HandlerPoster extends Handler implements Poster {
    // 事件队列
    private final PendingPostQueue queue;
    // 处理消息最大间隔时间 默认10ms,每次循环发布消息的时间超过该值时,就会让出主线程的使用权,等待下次调度再继续发布事件
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    // 此 Handle 是否活跃
    private boolean handlerActive;

    /**
     * 唯一构造
     *
     * @param eventBus                     EventBus
     * @param looper                       Looper 主线程的 Looper
     * @param maxMillisInsideHandleMessage int 超时时间
     */
    public HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
    super(looper);
    this.eventBus = eventBus;
    this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
    queue = new PendingPostQueue();
}

    /**
     * 入队
     *
     * @param subscription Subscription 接收事件的订阅方法
     * @param event        Object 将发布给订阅者的事件
     */
    public void enqueue(Subscription subscription, Object event) {
        // 获取一个 PendingPost,实际上将 subscription、event 包装成为一个 PendingPost
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        // 加锁 监视器为当前对象
        synchronized (this) {
            // 将获取到的 PendingPost 包装类入队
            queue.enqueue(pendingPost);
            // 判断此发布器是否活跃,如果活跃就不执行,等待 Looper 调度上一个消息,重新进入发布处理
            if (!handlerActive) {
                // 将发布器设置为活跃状态
                handlerActive = true;
                // sendMessage
                // 划重点!!!
                // 此处没有使用 new Message(),而是使用了 obtainMessage(),该方法将从全局的消息对象池中复用旧的对象,这比直接创建要更高效
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    /**
     * 处理消息
     * 该 Handle 的工作方式为:
     *
     */
    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            // 获取一个开始时间
            long started = SystemClock.uptimeMillis();
            // 死循环
            while (true) {
                // 取出队列中最前面的元素
                PendingPost pendingPost = queue.poll();
                // 接下来会进行两次校验操作
                // 判空 有可能队列中已经没有元素
                if (pendingPost == null) {
                    // 再次检查,这次是同步的
                    synchronized (this) {
                        // 继续取队头的元素
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            // 还是为 null,将处理状态设置为不活跃,跳出循环
                            handlerActive = false;
                            return;
                        }
                    }
                }
                // 调用订阅者方法
                eventBus.invokeSubscriber(pendingPost);
                // 获得方法耗时
                long timeInMethod = SystemClock.uptimeMillis() - started;
                // 判断本次调用的方法耗时是否超过预设值
                if (timeInMethod >= maxMillisInsideHandleMessage) {
                    // 发送进行下一次处理的消息,为了不阻塞主线程,暂时交出主线程使用权,并且发布消息到Looper,等待下一次调度再次进行消息的发布操作
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    // 设置为活跃状态
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            // 更新 Handle 状态
            handlerActive = rescheduled;
        }
    }
}

可以看到其实就是利用了Handler机制。

总结

总结就是,你看到总结了。说明你注定会变强!!!!!!!!

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

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

相关文章

掌握Python的X篇_10+11_if分支语句、else语句、elif语句

文章目录 1. if关键字及语法2. 语句块的概念3. else语句4. elif语句 1. if关键字及语法 基本语法如下&#xff1a; if 条件表达式:条件为True时&#xff0c;要执行的语句举例&#xff1a; number int(input("Input an number")) if number > 5 :print("这…

【Spring框架】spring对象注入的三种方法

目录 1.属性注入问题&#xff1a;同类型的Bean存储到容器多个&#xff0c;获取时报错的问题&#xff1b;1.将属性的名字和Bean的名字对应上。2.使用AutoWiredQualifier来筛选bean对象&#xff1b; 属性注入优缺点 2.Setter注入Setter注入优缺点 3.构造方法注入&#xff08;Spri…

Node.js 安装与版本管理(nvm 的使用)

安装 Node.js Node.js 诞生于 2009 年 5 月&#xff0c;截至今天&#xff08;2022 年 3 月 26 号&#xff09;的最新版本为 16.14.2 LTS 和 17.8.0 Current&#xff0c;可以去官网下载合适的版本。 其中&#xff0c;LTS&#xff08;Long Term Support&#xff09; 是长期维护…

MySQL基础扎实——主键与候选键

词义解释 主键&#xff08;Primary Key&#xff09;和候选键&#xff08;Candidate Key&#xff09;是关系型数据库中的术语&#xff0c;用于标识和唯一确定表中的记录。它们之间有以下区别&#xff1a; 唯一性&#xff1a;主键是表中的唯一标识&#xff0c;每个表只能有一个主…

环境保护数据传输系统监测环境指标

嵌入式实时操作系统&#xff08;RTOS&#xff09;是一种专门设计用于嵌入式系统的操作系统。它具有实时性能和低延迟的特点&#xff0c;能够满足对时间响应性要求较高的应用。本文介绍了一种具备Modbus Slave和Modbus Master功能的嵌入式实时操作系统设备&#xff0c;以及其扩展…

OpenGL Metal Shader 编程:ShaderToy 内置全局变量

OpenGL & Metal Shader 编程&#xff1a;ShaderToy 内置全局变量 前面发了一些关于 Shader 编程的文章&#xff0c;有读者反馈太碎片化了&#xff0c;希望这里能整理出来一个系列&#xff0c;方便系统的学习一下 Shader 编程。 由于主流的 Shader 编程网站&#xff0c;如…

Dart - 语法糖(持续更新)

文章目录 前言开发环境中间表示语法糖1. 操作符/运算符&#xff08;?./??/??/../?../.../...?&#xff09;2. 循环&#xff08;for-in&#xff09;3. 函数/方法&#xff08;>&#xff09;4. 关键字&#xff08;await for&#xff09; 最后 前言 通过将dill文件序列化…

【时间序列预测 2023 ICLR】TimesNet

【时间序列预测 2023 ICLR】TimesNet 论文题目&#xff1a;TIMESNET: TEMPORAL 2D-VARIATION MODELING FOR GENERAL TIME SERIES ANALYSIS 中文题目&#xff1a;TimesNet:用于一般时间序列分析的时态二维变异建模 论文链接&#xff1a;https://arxiv.org/abs/2210.02186 论文代…

大专同事一个人7天完成CRM系统开发,怪不得月薪3w

在当今的快节奏商业环境中&#xff0c;成功的关键是敏捷和高效。这使得开发自己的客户关系管理系统&#xff08;CRM&#xff09;成为许多企业的迫切需求。CRM有助于跟踪和优化销售线索&#xff0c;管理客户交互&#xff0c;并提高业务效率。但是&#xff0c;对于许多企业来说&a…

大学毕业后,我就去当了2个月外卖骑手,哭了一整晚

先简单介绍一下自己&#xff0c;我来自长沙&#xff0c;大学学的的物流管理专业&#xff0c;现在就职于一家互联网公司&#xff0c;从事软件测试工作。 我来自长沙县的一个偏远农村&#xff0c;家里兄弟姐妹多&#xff0c;父母无力负担我的学费&#xff0c;很多时候学费都是靠姐…

智慧工厂4G+蓝牙+UWB+RTK人员定位系统解决方案

人员定位在智慧工厂的应用正逐渐受到重视&#xff0c;通过使用现代化的技术和智能终端设备&#xff0c;工厂管理者能够实时定位和跟踪员工的位置&#xff0c;方便进行人员调度管理和监督人员的工作情况&#xff1b;人员遇到紧急情况&#xff0c;可通过定位设备一键报警求救&…

智慧消防:如何基于视频与智能分析技术搭建可视化风险预警平台?

一、背景分析 消防安全是一个重要的话题&#xff0c;涉及到每个人的生活和安全。每年都会发生大量的火灾&#xff0c;给人们带来极大的危害&#xff0c;摧毁了大量的财产&#xff0c;甚至造成了可怕的人员伤亡。而消防安全监督管理部门人员有限&#xff0c;消防安全监管缺乏有…

2.2 模型与材质基础

一、渲染管线与模型基础 1. 渲染管线 可编程阶段&#xff08;蓝色区域&#xff09;&#xff1a; 1顶点着色器 2几何着色器 3片元着色器 2. 模型的实现原理 UV&#xff1a;在建模软件中&#xff0c;进行UV展开&#xff0c;UV会放在一个横向为U纵向为V&#xff0c;范围&#xff0…

TEE GP(Global Platform)功能认证实验室

TEE之GP(Global Platform)认证汇总 GP认证实验室主要面向功能认证、SE安全认证、TEE安全认证&#xff0c;对于TEE来说&#xff0c;则分为TEE功能认证和TEE安全认证。本文对功能认证相关实验室机构进行总结和介绍。 一、国内3家 二、国外3家 参考&#xff1a; GlobalPlatform …

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测

回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测 目录 回归预测 | MATLAB实现POA-CNN-BiLSTM鹈鹕算法优化卷积双向长短期记忆神经网络多输入单输出回归预测预测效果基本介绍模型描述程序设计参考资料 预测效果 基本介绍 MATLA…

安全杂记 - js中的this关键字

javascript里什么是this this是js中的一个关键字&#xff0c;它是函数在运行时生成的一个内部对象&#xff0c;是属性和方法。 this就是属性或方法“当前”所在的对象&#xff0c;也就是调用函数的那个对象 this的使用场合 1.函数调用 <script>var a100;function test…

jenkins部署vue项目

文章目录 1 先安装node.js上传到linux并解压 jenkins 创建任务执行脚本 build Stepsvue jenkins shell 脚本 (jenkins-web.sh)启动 如何在linux上安装jenkins可以看上一篇 linux安装jenkins(详细步骤) 1 先安装node.js 建议最好跟你本地安装版本一致,减少没有必要的麻烦:node.…

UCloud上线可商用LLaMA2镜像,助力AGI应用发展

随着人工智能技术的快速发展&#xff0c;大模型应用在自然语言处理、图像识别、智能交互等领域展现出了巨大的潜力&#xff0c;为企业带来了更多创新和商机。众多企业纷纷将大模型应用于产品开发和业务优化中&#xff0c;希望通过提升智能化水平和用户体验来赢得竞争优势。近日…

【黑马头条之文章详情-静态文件生成】

本笔记内容为黑马头条项目的文章详情-静态文件生成部分 目录 一、思路分析 二、实现步骤 基础知识可参考下面两篇文章 【黑马头条之对象存储服务MinIO】_蛋饼吧的博客-CSDN博客 【黑马头条之freemarker入门】_蛋饼吧的博客-CSDN博客 一、思路分析 文章端创建app相关文章时…

如何获得SOLIDWORKS全认证资格证书!

SolidWorks认证考试 完成SolidWorks认证考试并获得专业SolidWorks认证是一项伟大的成就。它不仅在你的简历上自豪地闪耀&#xff0c;而且还向雇主、决策者和同事展示了你对软件的技能和知识水平。公司通常会将某些策略和标准设定为一项业务&#xff0c;以帮助保持在竞争中的领头…