EventBus反射原理

725 阅读4分钟

EventBus

EventBus使用很简单,在build.gradle中添加依赖

implementation 'org.greenrobot:eventbus:3.2.0'

然后按照下面官方推荐的方式,就可以上手了:

event-steps.png

在不配置annotationProcessor的情况下,EventBus是通过反射来实现消息传递,配置annotationProcessor后是主要通过apt生成文件来辅助实现(官方也更加推荐使用这种方式,当然了apt这种方式还是有少量的反射在里面)。下面是官方的相关描述

event-bus-apt.png

反射

今天讲讲EventBus如何通过反射实现。首先要解决的第一问题是找到订阅的方法。

怎么找接收事件的方法?

答:在接收事件的方法上添加注释标识,有了注解标识后,就能通过类信息过滤获取到。

先定义一个@Subscribe

Subscribe.kt

@Target(
    AnnotationTarget.FUNCTION
)
@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
// threadMode 定义接收事件的线程
annotation class Subscribe(val threadMode: ThreadMode = ThreadMode.POSTING)

Subscribe注解,接收一个ThreadMode参数,用于指定接收事件的线程。ThreadMode是一个枚举类。 定义如下

ThreadMode.kt

/**
 * 线程状态
 */
enum class ThreadMode {
    POSTING, // 事件的处理在和事件的发送在相同的进程,所以事件处理时间不应太长,不然影响事件的发送线程,而这个线程可能是UI线程
    MAIN,   // 事件的处理会在UI线程中执行,事件处理不应太长时间
    BACKGROUND,  // 后台进程,处理如保存到数据库等操作
    ASYNC // 异步执行,另起线程操作。事件处理会在单独的线程中执行,主要用于在后台线程中执行耗时操作
}

注解标识有了,下一步提取订阅方法。

EventBus.kt

class EventBus private constructor() {
    // 用来保存这些带注解的方法(订阅者的回调方法),MethodManager是封装类
    var cacheMap: HashMap<Any, List<MethodManager>> = HashMap()

    //注册遍历
    fun register(getter: Any) {
        var methodList = cacheMap[getter]
        //如果之前没有遍历过,就遍历,然后保存
        if (methodList == null) {
            methodList = findAnnotationMethod(getter)
            cacheMap[getter] = methodList
        }
    }

    private fun findAnnotationMethod(getter: Any): List<MethodManager> {
        val methodList: ArrayList<MethodManager> = ArrayList()

        // 获取类  com.loveqrc.eventbus.MainActivity
        var clazz: Class<*>? = getter.javaClass
        // 获取类的所有方法
        val methods = clazz!!.methods

        while (clazz != null) {
            val clazzName = clazz.name

            //循环遍历终止条件
            if (clazzName.startsWith("java.") || clazzName.startsWith("javax.")
                || clazzName.startsWith("android.") || clazzName.startsWith("androidx.")
            ) {
                break
            }

            for (method in methods) {
                // 注解为空的,那么就不是订阅的方法
                val subscribe = method.getAnnotation(Subscribe::class.java) ?: continue

                // public final void com.loveqrc.eventbus.MainActivity.message(java.lang.String)
                //方法的参数
                val parameterTypes = method.parameterTypes

                if (parameterTypes.size != 1) {
                    throw RuntimeException(method.name + "方法必需有且只有一个参数")
                }


                // 完全符合要求、规范的方法,保存到方法对象中MethodManager(3个重要成员:方法、参数、线程)
                // MethodManager为方法的封装类
                methodList.add(MethodManager(parameterTypes[0], subscribe.threadMode, method))

            }
            clazz = clazz.superclass
        }


        return methodList
    }


    companion object {

        @Volatile
        private var instance: EventBus? = null

        @JvmStatic
        fun getDefault() = instance ?: synchronized(this) {
            instance ?: EventBus().also {
                instance = it
            }
        }
    }

}
  

通过EventBus.getDefault()获取单例对象,然后调用register方法,通过注解找到订阅方法。其中MethodManager只是一个封装类,为后续调用EventBus.getDefault().post("")方法提供需要的信息。

MethodManager.kt

/**
 * 保存符合要求的订阅方法封装类
 */
class MethodManager(
    // 订阅者的回调方法(注解方法)的参数类型
    //  threadMode :订阅者的回调方法(注解方法)的线程模式
    // method: 订阅者的回调方法(注解方法)
    var type: Class<*>, var threadMode: ThreadMode, var method: Method
) {

    @NonNull
    override fun toString(): String {
        return "MethodManager{" +
                "type=" + type +
                ", threadMode=" + threadMode +
                ", method=" + method +
                '}'
    }

}

进行post消息传递

有了订阅者的信息,进行post消息传递就很简单了

EventBus.kt

class EventBus private constructor() {
    ......
    
    var handler: Handler = Handler(Looper.getMainLooper())

    // 创建一个子线程(缓存线程池)
    var executorService: ExecutorService = Executors.newCachedThreadPool()
    
    ......
   }

新增两个成员变量,用于切换到订阅者需要的线程。

EventBus.kt

class EventBus private constructor() {
    ......
    
   fun post(setter: Any) {
        // 订阅者已经登记,从登记表中找出
        val keys = cacheMap.keys

        for (key in keys) {
            // 比如获取MainActivity对象
            // 获取MainActivity中所有注解的方法
            val methodList = cacheMap[key] ?: continue

            // 循环每个方法
            for (method in methodList) {
                // 有可能多个方法的参数一样,从而都同时收到发送的消息
                if (method.type.isAssignableFrom(setter.javaClass)) {

                    // 通过方法的类型匹配,从SecondActivity发送的对象(参数)
                    // 匹配MainActivity中所有注解的方法符合要求的,都发送消息

                    // class1.isAssignableFrom(class2) 判定此 Class 对象所表示的类或接口
                    // 与指定的 Class 参数所表示的类或接口是否相同,或是否是其超类或超接口


                    // 线程调度
                    when (method.threadMode) {
                        ThreadMode.POSTING -> {
                            invoke(method, key, setter)
                        }
                        ThreadMode.MAIN -> {
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                invoke(method, key, setter)
                            } else {
                                handler.post {
                                    invoke(method, key, setter);
                                }
                            }
                        }
                        ThreadMode.BACKGROUND -> {
                            if (Looper.myLooper() == Looper.getMainLooper()) {
                                executorService.execute {
                                    invoke(method, key, setter)
                                }
                            } else {
                                invoke(method, key, setter)
                            }
                        }
                        else -> {
                            //do nothing
                        }
                    }

                }
            }

        }

    }

    // 找到匹配方法后,通过反射调用MainActivity中所有符合要求的方法
    private fun invoke(method: MethodManager, getter: Any, setter: Any) {
        val execute = method.method
        try {
            execute.invoke(getter, setter)
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }
    
    ......
   }

代码的注释已经很明了,在cacheMap中,找到符合的订阅方法,然后反射调用。

总结

总体来说EventBus反射的方式原理还是相对简单的,下一步分析如何通过apt实现EventBus功能。