三方庫源碼筆記(2)-EventBus 本身實現一個

對於 Android Developer 來講,不少開源庫都是屬於開發必備的知識點,從使用方式到實現原理再到源碼解析,這些都須要咱們有必定程度的瞭解和運用能力。因此我打算來寫一系列關於開源庫源碼解析實戰演練的文章,初定的目標是 EventBus、ARouter、LeakCanary、Retrofit、Glide、OkHttp、Coil 等七個知名開源庫,但願對你有所幫助 😇😇java

公衆號:字節數組git

系列文章導航:github

上一篇文章中對 EventBus 進行了一次全面的源碼解析,原理懂得了,那麼也須要來進行一次實戰才行。對於一個優秀的第三方庫,開發者除了要學會如何使用外,更有難度的用法就是去了解實現原理,懂得如何改造甚至是本身實現。本篇文章就來本身動手實現一個 EventBus,不求功能多齊全,就來實現簡單的註冊、反註冊、發送消息、接收消息這些功能便可 😇😇api

先來看下最終的實現效果數組

對於如下兩個監聽者:EasyEventBusMainActivity 和 EasyEventBusTest,經過標註 @Event註解來修飾監聽方法,而後使用 EasyEventBus 這個自定義類來進行註冊、反註冊和發送消息markdown

/** * @Author: leavesC * @Date: 2021/1/15 23:42 * @Desc: * @Github:https://github.com/leavesC */
class EasyEventBusMainActivity : BaseActivity() {

    override val bind by getBind<ActivityEasyEventBusMainBinding>()

    private val eventTest = EasyEventBusTest()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        EasyEventBus.register(this)
        eventTest.register()
        bind.btnPostString.setOnClickListener {
            EasyEventBus.post("Hello")
        }
        bind.btnPostBean.setOnClickListener {
            EasyEventBus.post(HelloBean("hi"))
        }
    }

    @Event
    fun stringFun(msg: String) {
        showToast("$msg ${this.javaClass.simpleName}")
    }

    @Event
    fun benFun(msg: HelloBean) {
        showToast("${msg.data} ${this.javaClass.simpleName}")
    }

    override fun onDestroy() {
        super.onDestroy()
        EasyEventBus.unregister(this)
        eventTest.unregister()
    }

}

class EasyEventBusTest {

    @Event
    fun stringFun(msg: String) {
        showToast("$msg ${this.javaClass.simpleName}")
    }

    @Event
    fun benFun(msg: HelloBean) {
        showToast("${msg.data} ${this.javaClass.simpleName}")
    }

    fun register() {
        EasyEventBus.register(this)
    }

    fun unregister() {
        EasyEventBus.unregister(this)
    }

}

data class HelloBean(val data: String)
複製代碼

使用起來和真正的 EvnetBus 差很少 😁😁,雖然其實是要簡陋不少的~~ide

最終自定義的 EasyEventBus 也只有五十行左右的代碼量,僅向外部提供了三個方法用於進行註冊、反註冊和發送消息oop

/** * @Author: leavesC * @Date: 2020/10/3 11:44 * @Desc: * @Github:https://github.com/leavesC */
object EasyEventBus {

    private val subscriptions = mutableSetOf<Any>()

    private const val PACKAGE_NAME = "github.leavesc.easyeventbus"

    private const val CLASS_NAME = "EventBusInject"

    private const val CLASS_PATH = "$PACKAGE_NAME.$CLASS_NAME"

    private val clazz = Class.forName(CLASS_PATH)

    //經過反射生成 EventBusInject 對象
    private val instance = clazz.newInstance()

    @Synchronized
    fun register(subscriber: Any) {
        subscriptions.add(subscriber)
    }

    @Synchronized
    fun unregister(subscriber: Any) {
        subscriptions.remove(subscriber)
    }

    @Synchronized
    fun post(event: Any) {
        subscriptions.forEach { subscriber ->
            val subscriberInfo =
                getSubscriberInfo(subscriber.javaClass)
            if (subscriberInfo != null) {
                val methodList = subscriberInfo.methodList
                methodList.forEach { method ->
                    if (method.eventType == event.javaClass) {
                        val declaredMethod = subscriber.javaClass.getDeclaredMethod(
                            method.methodName,
                            method.eventType
                        )
                        declaredMethod.invoke(subscriber, event)
                    }
                }
            }
        }
    }

    //經過反射調用 EventBusInject 的 getSubscriberInfo 方法
    private fun getSubscriberInfo(subscriberClass: Class<*>): SubscriberInfo? {
        val method = clazz.getMethod("getSubscriberInfo", Class::class.java)
        return method.invoke(instance, subscriberClass) as? SubscriberInfo
    }

}
複製代碼

1、怎麼實現

這裏先來想下這個自定義的 EasyEventBus 應該實現什麼功能,以及怎麼實現源碼分析

EasyEventBus 的核心重點就在於其經過註解處理器生成輔助文件這個過程,這個過程使用者是感知不到的,這塊邏輯也只會在編譯階段被觸發到。咱們但願在編譯階段就可以拿到全部聲明瞭 @Event 的方法,省得在運行時纔來反射,即在編譯階段就但願可以生成如下的輔助文件:post

/** * 這是自動生成的代碼 by leavesC */
public class EventBusInject {

    private static final Map<Class<?>, SubscriberInfo> subscriberIndex = new HashMap<Class<?>, SubscriberInfo>();

    {
        List<EventMethodInfo> eventMethodInfoList = new ArrayList<EventMethodInfo>();
        eventMethodInfoList.add(new EventMethodInfo("stringFun", String.class));
        eventMethodInfoList.add(new EventMethodInfo("benFun", HelloBean.class));
        SubscriberInfo subscriberInfo = new SubscriberInfo(EasyEventBusMainActivity.class, eventMethodInfoList);
        putIndex(subscriberInfo);
    }

    {
        List<EventMethodInfo> eventMethodInfoList = new ArrayList<EventMethodInfo>();
        eventMethodInfoList.add(new EventMethodInfo("stringFun", String.class));
        eventMethodInfoList.add(new EventMethodInfo("benFun", HelloBean.class));
        SubscriberInfo subscriberInfo = new SubscriberInfo(EasyEventBusTest.class, eventMethodInfoList);
        putIndex(subscriberInfo);
    }

    private static final void putIndex(SubscriberInfo info) {
        subscriberIndex.put(info.getSubscriberClass(), info);
    }

    public final SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
        return subscriberIndex.get(subscriberClass);
    }
}
複製代碼

能夠看到,subscriberIndex中存儲了全部監聽方法的簽名信息,在應用運行時咱們咱們只須要經過 getSubscriberInfo 方法就能夠拿到 subscriberClass 的全部監聽方法

最後,還須要向外提供一個 API 調用入口,即上面貼出來的自定義的 EasyEventBus 這個自定義類,是提供給使用者運行時調用的,在有消息須要發送的時候經過外部傳入的 subscriberClass 從 EventBusInject 取出全部監聽方法進行反射回調

因此,EasyEventBus 邏輯上會拆分爲兩個 moudle:

  • easyeventbus_api。向外暴露 EasyEventBus 和 @Event
  • easyeventbus_processor。不對外暴露,只在編譯階段生效

2、註解處理器

首先,咱們須要提供一個註解對監聽方法進行標記

@MustBeDocumented
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.FUNCTION)
annotation class Event
複製代碼

而後,咱們在編譯階段須要預先把全部監聽方法抽象保存起來,因此就須要定義兩個 JavaBean 來做爲承載體

/** * @Author: leavesC * @Date: 2020/10/3 17:33 * @Desc: * @Github:https://github.com/leavesC */
data class EventMethodInfo(val methodName: String, val eventType: Class<*>)

data class SubscriberInfo(
    val subscriberClass: Class<*>,
    val methodList: List<EventMethodInfo>
)
複製代碼

而後聲明一個 EasyEventBusProcessor 類繼承於 AbstractProcessor,由編譯器在編譯階段傳入咱們關心的代碼元素

/** * @Author: leavesC * @Date: 2020/10/3 15:55 * @Desc: * @Github:https://github.com/leavesC */
class EasyEventBusProcessor : AbstractProcessor() {

    companion object {

        private const val PACKAGE_NAME = "github.leavesc.easyeventbus"

        private const val CLASS_NAME = "EventBusInject"

        private const val DOC = "這是自動生成的代碼 by leavesC"

    }

    private lateinit var elementUtils: Elements

    private val methodsByClass = LinkedHashMap<TypeElement, MutableList<ExecutableElement>>()

    override fun init(processingEnvironment: ProcessingEnvironment) {
        super.init(processingEnvironment)
        elementUtils = processingEnv.elementUtils
    }

    override fun getSupportedAnnotationTypes(): MutableSet<String> {
        //只須要處理 Event 註解
        return mutableSetOf(Event::class.java.canonicalName)
    }

    override fun getSupportedSourceVersion(): SourceVersion {
        return SourceVersion.RELEASE_8
    }

    ···
   
}
複製代碼

經過 collectSubscribers方法拿到全部的監聽方法,保存到 methodsByClass中,同時須要對方法簽名進行校驗:只能是實例方法,且必須是 public 的,最多且至少包含一個入參參數

override fun process( set: Set<TypeElement>, roundEnvironment: RoundEnvironment ): Boolean {
        val messager = processingEnv.messager
        collectSubscribers(set, roundEnvironment, messager)
        if (methodsByClass.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Event annotations found")
        } else {
            
           ···
            
        }
        return true
    }

    private fun collectSubscribers( annotations: Set<TypeElement>, env: RoundEnvironment, messager: Messager ) {
        for (annotation in annotations) {
            val elements = env.getElementsAnnotatedWith(annotation)
            for (element in elements) {
                if (element is ExecutableElement) {
                    if (checkHasNoErrors(element, messager)) {
                        val classElement = element.enclosingElement as TypeElement
                        var list = methodsByClass[classElement]
                        if (list == null) {
                            list = mutableListOf()
                            methodsByClass[classElement] = list
                        }
                        list.add(element)
                    }
                } else {
                    //@Event 只能用於修改方法
                    messager.printMessage(
                        Diagnostic.Kind.ERROR,
                        "@Event is only valid for methods",
                        element
                    )
                }
            }
        }
    }

    /** * 校驗方法簽名是否合法 */
    private fun checkHasNoErrors(element: ExecutableElement, messager: Messager): Boolean {
        //不能是靜態方法
        if (element.modifiers.contains(Modifier.STATIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Event method must not be static", element)
            return false
        }
        //必須是 public 方法
        if (!element.modifiers.contains(Modifier.PUBLIC)) {
            messager.printMessage(Diagnostic.Kind.ERROR, "Event method must be public", element)
            return false
        }
        //方法最多且只能包含一個參數
        val parameters = element.parameters
        if (parameters.size != 1) {
            messager.printMessage(
                Diagnostic.Kind.ERROR,
                "Event method must have exactly 1 parameter",
                element
            )
            return false
        }
        return true
    }
複製代碼

而後,再來生成 subscriberIndex 這個靜態常量,以及對應的靜態方法塊、putIndex 方法

//生成 subscriberIndex 這個靜態常量
    private fun generateSubscriberField(): FieldSpec {
        val subscriberIndex = ParameterizedTypeName.get(
            ClassName.get(Map::class.java),
            getClassAny(),
            ClassName.get(SubscriberInfo::class.java)
        )
        return FieldSpec.builder(subscriberIndex, "subscriberIndex")
            .addModifiers(
                Modifier.PRIVATE,
                Modifier.STATIC,
                Modifier.FINAL
            )
            .initializer(
                "new ${"$"}T<Class<?>, ${"$"}T>()",
                HashMap::class.java,
                SubscriberInfo::class.java
            )
            .build()
    }

    //生成靜態方法塊
    private fun generateInitializerBlock(builder: TypeSpec.Builder) {
        for (item in methodsByClass) {
            val methods = item.value
            if (methods.isEmpty()) {
                break
            }
            val codeBuilder = CodeBlock.builder()
            codeBuilder.add(
                "${"$"}T<${"$"}T> eventMethodInfoList = new ${"$"}T<${"$"}T>();",
                List::class.java,
                EventMethodInfo::class.java,
                ArrayList::class.java,
                EventMethodInfo::class.java
            )
            methods.forEach {
                val methodName = it.simpleName.toString()
                val eventType = it.parameters[0].asType()
                codeBuilder.add(
                    "eventMethodInfoList.add(new EventMethodInfo(${"$"}S, ${"$"}T.class));",
                    methodName,
                    eventType
                )
            }
            codeBuilder.add(
                "SubscriberInfo subscriberInfo = new SubscriberInfo(${"$"}T.class, eventMethodInfoList); putIndex(subscriberInfo);",
                item.key.asType()
            )
            builder.addInitializerBlock(
                codeBuilder.build()
            )
        }
    }

	//生成 putIndex 方法
	private fun generateMethodPutIndex(): MethodSpec {
        return MethodSpec.methodBuilder("putIndex")
            .addModifiers(Modifier.PRIVATE, Modifier.STATIC, Modifier.FINAL)
            .returns(Void.TYPE)
            .addParameter(SubscriberInfo::class.java, "info")
            .addCode(
                CodeBlock.builder().add("subscriberIndex.put(info.getSubscriberClass() , info);")
                    .build()
            )
            .build()
    }
複製代碼

而後,再來生成 getSubscriberInfo 這個公開方法,用於運行時調用

//生成 getSubscriberInfo 方法
    private fun generateMethodGetSubscriberInfo(): MethodSpec {
        return MethodSpec.methodBuilder("getSubscriberInfo")
            .addModifiers(Modifier.PUBLIC, Modifier.FINAL)
            .returns(SubscriberInfo::class.java)
            .addParameter(getClassAny(), "subscriberClass")
            .addCode(
                CodeBlock.builder().add("return subscriberIndex.get(subscriberClass);")
                    .build()
            )
            .build()
    }
複製代碼

完成以上方法的定義後,就能夠在 process 方法中完成 EventBusInject 整個類文件的構建了

override fun process( set: Set<TypeElement>, roundEnvironment: RoundEnvironment ): Boolean {
        val messager = processingEnv.messager
        collectSubscribers(set, roundEnvironment, messager)
        if (methodsByClass.isEmpty()) {
            messager.printMessage(Diagnostic.Kind.WARNING, "No @Event annotations found")
        } else {
            val typeSpec = TypeSpec.classBuilder(CLASS_NAME)
                .addModifiers(Modifier.PUBLIC)
                .addJavadoc(DOC)
                .addField(generateSubscriberField())
                .addMethod(generateMethodPutIndex())
                .addMethod(generateMethodGetSubscriberInfo())
            generateInitializerBlock(typeSpec)
            val javaFile = JavaFile.builder(PACKAGE_NAME, typeSpec.build())
                .build()
            try {
                javaFile.writeTo(processingEnv.filer)
            } catch (e: Throwable) {
                e.printStackTrace()
            }
        }
        return true
    }
複製代碼

3、EasyEventBus

EasyEventBus 的邏輯就很簡單了,主要是經過反射來生成 EventBusInject 對象,拿到 subscriber 關聯的 SubscriberInfo,而後在有消息被 Post 出來的時候進行遍歷調用便可

/** * @Author: leavesC * @Date: 2020/10/3 11:44 * @Desc: * @Github:https://github.com/leavesC */
object EasyEventBus {

    private val subscriptions = mutableSetOf<Any>()

    private const val PACKAGE_NAME = "github.leavesc.easyeventbus"

    private const val CLASS_NAME = "EventBusInject"

    private const val CLASS_PATH = "$PACKAGE_NAME.$CLASS_NAME"

    private val clazz = Class.forName(CLASS_PATH)

    //經過反射生成 EventBusInject 對象
    private val instance = clazz.newInstance()

    @Synchronized
    fun register(subscriber: Any) {
        subscriptions.add(subscriber)
    }

    @Synchronized
    fun unregister(subscriber: Any) {
        subscriptions.remove(subscriber)
    }

    @Synchronized
    fun post(event: Any) {
        subscriptions.forEach { subscriber ->
            val subscriberInfo =
                getSubscriberInfo(subscriber.javaClass)
            if (subscriberInfo != null) {
                val methodList = subscriberInfo.methodList
                methodList.forEach { method ->
                    if (method.eventType == event.javaClass) {
                        val declaredMethod = subscriber.javaClass.getDeclaredMethod(
                            method.methodName,
                            method.eventType
                        )
                        declaredMethod.invoke(subscriber, event)
                    }
                }
            }
        }
    }

    //經過反射調用 EventBusInject 的 getSubscriberInfo 方法
    private fun getSubscriberInfo(subscriberClass: Class<*>): SubscriberInfo? {
        val method = clazz.getMethod("getSubscriberInfo", Class::class.java)
        return method.invoke(instance, subscriberClass) as? SubscriberInfo
    }

}
複製代碼

4、GitHub

文本實現的 EasyEventBus 挺簡陋的😂😂由於個人想法也只是經過本身動手來加深對 EventBus 的理解而已,這裏也提供上述代碼的 GitHub 連接:AndroidOpenSourceDemo

相關文章
相關標籤/搜索