Take you to a Kotlin version of EventBus

Preface

EventBus was used by many people in the first two years. It was contributed by the greenrobot organization, which also contributed GreenDao (currently not recommended, it is recommended to use the official Room database framework). The function of EventBus is very simple. It simplifies the delivery of Android events by decoupling publishers and subscribers. Simply put, it can replace Android's traditional Intent, Handler, Broadcast or interface functions to transfer data between Activity, Fragment, and Service. But then RxBus (relying on RxJava and RxAndroid) appeared. It only took a few dozen lines of code to shake the status of EventBus. It can be achieved with dozens of lines of code, and it is more convenient to follow the life cycle without relying on third-party packages. . . . But these are not the focus of this article. Although RxBus and LiveDataBus are currently the first choices, EventBus must have its own advantages when it has ruled the world for so long, so this article will pick up an EventBus to pick up the big brother EventBus.

EventBus introduction

This is the Github address of EventBus: https://github.com/greenrobot/EventBus.

There are many advantages of EventBus (now it is not an advantage): the code is simple, it is a publish and subscribe design pattern (observer design pattern), which simplifies the communication between components, separates the sender and receiver of the event, And you can switch threads at will, avoiding complicated and error-prone dependencies and life cycle issues.

Everyone should have used EventBus. When we use it, we generally need to write the registration and unregister first, then define the receiving method, write a comment on the receiving method, and pass the object that needs to be passed through the Post method at the sending place. The receiving method just defined can receive the passed object, and we can modify the thread by annotation on the receiving method.

Having said so many advantages, let's take a look at the implementation principle of EventBus. Let's take a look at the schematic diagram of EventBus:

  • The bottom layer of EventBus uses annotation and reflection to obtain the subscription method information (first is the annotation acquisition, if the annotation cannot be obtained, then use reflection)
  • The current subscriber is added to the subscriptionByEventType collection of the total event subscribers of Eventbus
  • All the subscribed event types of the subscriber are added to typeBySubscriber to facilitate the removal of events when unregistering

Start to achieve

Having said that, it's time to start the text. First of all, let's imitate EventBus and come to a singleton. The singleton in Kotlin is very simple. Use the object keyword directly to modify the class, then this class is already the simplest lazy singleton. Of course, double check locks can also be added. The locking algorithm is not explained in detail here. Take a look at the code:

object EventBus {
    
    }

Simple, too simple, the next thing we need to do is to write down a few methods we need, think about it, usually when we call, there are generally only three methods: registration, de-registration and sending methods. It is very clear, then come again Define these three methods:

object EventBus {
    
    

    // 所有未解注册的缓存
    private val cacheMap: MutableMap<Any, List<SubscribeMethod>> = HashMap()

    /**
     * 注册
     * @param subscriber 注册的Activity
     */
    fun register(subscriber: Any) {
    
    }

    /**
     * 发送消息
     * @param obj 具体消息
     */
    fun post(obj: Any) {
    
    }

    /**
     * 取消注册
     * @param subscriber /
     */
    fun unRegister(subscriber: Any) {
    
     }

}

The above code also defines a Map. There is a small detail here. I use MutableMap instead of Map. This is because MutableMap is a mutable map and Map is immutable (List is also the same when used). This Map is added to save the registered class and the collection of methods that need to be received in the class. Well, yes, SubscribeMethod is the class of the subscription method. Let's look at the code of SubscribeMethod:

class SubscribeMethod(
    //注册方法
    var method: Method,
    //线程类型
    var threadModel: ThreadModel,
    //参数类型
    var eventType: Class<*>
)

The above code is the way of writing entity classes in Kotlin (you can also write default values ​​for parameters, and parameters with default values ​​can be passed without passing when constructing the class), so you can directly implement the get, set, and toString methods of the class, it's very simple ?

Next, it’s time to improve the methods in the EventBus class written above. Let’s first think about the registration method. The registration method will first go to the cache to find if it exists. If it exists, it proves that it has been registered, and then no operation is performed. If it does not exist, then Register, um, take a look at the code:

    /**
     * 注册
     * @param subscriber 注册的Activity
     */
    fun register(subscriber: Any) {
    
    
        var subscribeMethods = cacheMap[subscriber]
        // 如果已经注册,就不需要注册
        if (subscribeMethods == null) {
    
    
            subscribeMethods = getSubscribeList(subscriber);
            cacheMap[subscriber] = subscribeMethods;
        }
    }

You can see that there is a getSubscribeList method written in the above code . This method is to use the passed in class to perform loop reflection to obtain the eligible methods (that is, the annotated receiving method). Note here that the loop is because there may be Put the registration and de-registration in BaseActivity, then you need to cycle to facilitate the subclass and the parent class. Through analysis, the following code can be obtained:

		private fun getSubscribeList(subscriber: Any): List<SubscribeMethod> {
    
    
        val list: MutableList<SubscribeMethod> = arrayListOf()
        var aClass = subscriber.javaClass
        while (aClass != null) {
    
    
            aClass = aClass.superclass as Class<Any>
        }
        return list
    }

We have obtained all the registered classes through the above code, but here we need to filter out the system classes. It is definitely impossible for the system classes to register EventBus, so the following code is available:

//判断分类是在那个包下,(如果是系统的就不需要)
val name = aClass.name
if (name.startsWith("java.") ||
   name.startsWith("javax.") ||
   name.startsWith("android.") ||
   name.startsWith("androidx.")
) {
    
    
   break
}

After filtering the system class, you need to determine the method, get all the methods in the class through reflection, and then determine whether it is the receiving method we defined according to the annotation, and then construct the subscription method class we just defined and put it into the List:

val declaredMethods = aClass.declaredMethods
            declaredMethods.forEach {
    
    
                val annotation = it.getAnnotation(Subscribe::class.java) ?: return@forEach
                //检测是否合格
                val parameterTypes = it.parameterTypes
                if (parameterTypes.size != 1) {
    
    
                    throw RuntimeException("EventBus只能接收一个参数")
                }
                //符合要求
                val threadModel = annotation.threadModel

                val subscribeMethod = SubscribeMethod(
                    method = it,
                    threadModel = threadModel,
                    eventType = parameterTypes[0]
                )

                list.add(subscribeMethod)
            }

Get here to get the annotations in the methods we defined in the class, and directly return a List to the above registration method, and save the List in the cache cacheMap .

In the above code, it is judged whether there is the annotation Subscribe that we defined , but the annotation has not been defined yet. The definition of annotation in Java is to add the @ symbol before the interfac keyword, but not in Kotlin. The code is as follows:

@Target(AnnotationTarget.FUNCTION)
@Retention(value = AnnotationRetention.RUNTIME)
annotation class Subscribe(val threadModel: ThreadModel = ThreadModel.POSTING)

Target and Retention are the same scope and execution time as in Java. Everyone must have noticed that there is a ThreadModel above that we haven't defined yet. Let's talk about it later (switching threads).

The registration method is finished. Let’s write about the de-registration. The de-registration is very simple. If there is a corresponding value in the Map, you only need to remove the corresponding value in the Map. If not, no operation is required:

    /**
     * 取消注册
     * @param subscriber /
     */
    fun unRegister(subscriber: Any) {
    
    
        val list = cacheMap[subscriber]
        //如果获取到
        if (list != null) {
    
    
            cacheMap.remove(subscriber)
        }

    }

Next, it's time for today's core code. Pass the parameters to the receiving method through the Post method and execute it. The idea is very simple. Find all classes directly in the cache, then get the annotated method in the loop, and then judge whether the method should receive events based on the parameter type:

    /**
     * 发送消息
     * @param obj 具体消息
     */
    fun post(obj: Any) {
    
    
        val keys = cacheMap.keys
        val iterator = keys.iterator()
        while (iterator.hasNext()) {
    
    
            // 拿到注册类
            val next = iterator.next()
            //获取类中所有添加注解的方法
            val list = cacheMap[next]
            list?.forEach {
    
    
                //判断这个方法是否应该接收事件
                if (it.eventType.isAssignableFrom(obj::class.java)) {
    
    
                    //invoke需要执行的方法
                    invoke(it, next, obj)
                }
            }

        }
    }

The above code has been analyzed above, and there is also an invoke method that needs to be written. This method is very simple, that is, to execute the receiving method:

    /**
     * 执行接收消息方法
     * @param it 需要接收消息的方法
     * @param next 注册类
     * @param obj 接收的参数(即post的参数)
     */
    private fun invoke(it: SubscribeMethod, next: Any, obj: Any) {
    
    
        val method = it.method
        method.invoke(next, obj)
    }

So far, the basic EventBus function has been implemented, we can test it below.

test

Let’s take a simple example for the test. There are only two activities. Let’s look at the first one. There is only a TextView and a Button in the first. The TextView is used to display the value passed in for a while, and the Button is used to jump to The second Activity, register and unregister in the Activity, take a look at the code:

class MainActivity : AppCompatActivity() {
    
    

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

    private fun initView() {
    
    
        btnJump.setOnClickListener {
    
    
            startActivity(Intent(this, TwoActivity::class.java))
        }
    }

    override fun onDestroy() {
    
    
        super.onDestroy()//解注册
        EventBus.unRegister(this)
    }

}

You also need to add a receiving method, don’t forget to add a comment:

    @Subscribe
    fun zhu(person: Person) {
    
    
        tvText.text = "name=${
      
      person.name}   age=${
      
      person.age}"
    }

The first Activity is finished. Let’s write the second one. The second is simpler, with only one Button, which is used to Post an object:

class TwoActivity : AppCompatActivity() {
    
    

    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_two)
        initView()
    }

    private fun initView() {
    
    
        btnSendMessage.setOnClickListener {
    
    
            EventBus.post(Person(name = "朱江",age = 23))
        }
    }
}

Let's look at the Person class again:

class Person(var name: String, var age: Int)

Okay, everything is ready, just run
Insert picture description here
short , open the whole: we can find that the basic functions have been implemented, but there are still flaws, let's continue to look down.

Expand

The basic function is realized, but EventBus also has a very important function—switching threads. We can specify threads to execute in the receiving method. We have not realized it now. You can use the above code to test. The test method is very simple. Put the Post method directly in the child thread, and then play a toast in the receiving method:

    private fun initView() {
    
    
        btnSendMessage.setOnClickListener {
    
    
            Thread {
    
    
                EventBus.post(Person(name = "朱江",age = 23))
            }.start()
        }
    }
    @Subscribe
    fun zhu(person: Person) {
    
    
        Toast.makeText(this,"name=${
      
      person.name}age=${
      
      person.age}",Toast.LENGTH_LONG).show()
    }

Then take a look at the running results:
Insert picture description here
you can see that the application crashed directly. The reason for the crash is very simple, because we did not do thread switching, we put it in the child thread when Post, but the UI update operation was done in the receiving method, so it will definitely be collapse. So let's add a thread switch below.

It is also mentioned in the code above, ThreadModel class, mentioned in the comments above, this is an enumeration class, which defines some required threads, just look at the code:

enum class ThreadModel {
    
    

    // 默认模式,无论post是在子线程或主线程,接收方法的线程为post时的线程。
    // 不进行线程切换
    POSTING,

    // 主线程模式,无论post是在子线程或主线程,接收方法的线程都切换为主线程。
    MAIN,

    // 主线程模式,无论post是在子线程或主线程,接收方法的线程都切换为主线程。
    // 这个在EventBus源码中与MAIN不同, 事件将一直排队等待交付。这确保了post调用是非阻塞的。
    // 此处不做其他处理,直接按照主线程模式处理
    MAIN_ORDERED,

    // 子线程模式,无论post是在子线程或主线程,接收方法的线程都切换为子线程。
    ASYNC

}

So next we need to think about how to do thread switching? In fact, thread switching is only the thread where the receiving method exists. In fact, we only need to change the execution of the invoke in the Post method. Let’s talk about it:

when (it.threadModel) {
    
    
        ThreadModel.POSTING -> {
    
    
            //默认情况,不进行线程切换,post方法是什么线程,接收方法就是什么线程
            EventBus.invoke(it, next, obj)
        }
        // 接收方法在主线程执行的情况
        ThreadModel.MAIN, ThreadModel.MAIN_ORDERED -> {
    
    
            // Post方法在主线程执行的情况
            if (Looper.myLooper() == Looper.getMainLooper()) {
    
    
                EventBus.invoke(it, next, obj)
            } else {
    
    
                // 在子线程中接收,主线程中接收消息
                EventBus.handler.post {
    
     EventBus.invoke(it, next, obj) }
            }
        }
        //接收方法在子线程的情况
        ThreadModel.ASYNC -> {
    
    
            //Post方法在主线程的情况
            if (Looper.myLooper() == Looper.getMainLooper()) {
    
    
                EventBus.executorService.execute(Runnable {
    
    
                    EventBus.invoke(
                        it,
                        next,
                        obj
                    )
                })
            } else {
    
    
                //Post方法在子线程的情况
                EventBus.invoke(it, next, obj)
            }
        }
    }

The above code logic is not difficult, let’s briefly talk about it here, the default thread executes directly; if the main thread is the main thread, you need to determine whether the current thread is the main thread, if it is, execute it directly, if not, transfer it to the main thread and execute it through the Handler; If it is similar to the main thread, but the execution method needs to be put into the thread pool, so that it is executed in the child thread.

Next, modify the code just now and add an annotation to replace the main thread in the receiving method:

    @Subscribe(threadModel = ThreadModel.MAIN)
    fun zhu(person: Person) {
    
    
        //tvText.text = "name=${person.name}   age=${person.age}"
        Toast.makeText(this,"name=${
      
      person.name}   age=${
      
      person.age}",Toast.LENGTH_LONG).show()
    }

Let's run it again to see the effect:
Insert picture description here

It can be found that it has been successful, and we have also realized the function of thread switching. The method of use is the same as that of EventBus. Just make a note in the comment.

end

The article is basically over here. The amount of code above is actually not much. You can go to my Github to download the code and run it. You can try to switch the annotation thread. Finally, put the address of all the code in this article: https:/ /github.com/zhujiang521/EventBus

Guess you like

Origin blog.csdn.net/haojiagou/article/details/105363300