目录
我的学习手册 - 热更新了解了一下下
我的学习手册 - Glide了解了一下下
我的学习手册 - 进程保活了解了一下下
我的学习手册 - EventBus了解了一下下
我的学习手册 - ARouter了解了一下下
首先看下EventBus是如何使用的
EventBus.getDefault().register(this);
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {/* Do something */};
EventBus.getDefault().unregister(this);
EventBus.getDefault().post(new MessageEvent());
可以看到,我们在ActivityA中进行注册和解除EventBus,并且使用@Subscribe进行注解需要接收Event的方法。在ActivityB中发送数据Event,通过Event的类型来查找使用的方法,来进行数据的交互。通过简单的了解了一下EventBus,我们来尝试自己写一下EventBus。
EventBus其实就是一个数据集合,通过key和value的键值对存储数据,在合适的时机进行读取,存储和移除。key就是register()方法中传入的this,或者使用this.hashCode()都可以,只要保证唯一就行,value就是在注册体中使用@Subscribe注解的方法,由于可以有多个方法,所以使用HashSet来进行存储。可是注解的是方法,不是一个实体,无法直接调用这个方法,可以想到使用反射来解决这个问题,然后发现方法注解内部还有一个线程的使用,表示该方法在什么线程中进行调用,线程?Handler,Thread。所以与value的集合需要进行一个封装,需要三个参数class(方法体中传入的参数用于匹配post发送的参数),method(使用@Subscribe进行注解的方法),threadMode(线程调度的模式)。对于粘性sticky,只要将两者的方案调换,post进行存储,register进行调用。好的基本思路就是这样
一、创建封装类
SubscribeMethod
class SubscribeMethod(
//需要调用的方法体
val mMethod: Method,
//接收的线程
val mThread: ThreadMode,
//数据类型
val mClazz: Class<*>
)
Subscribe注解
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class Subscribe(
//线程
val threadMode: ThreadMode = ThreadMode.MAIN,
//是否粘性
val isSticky: Boolean = false
)
ThreadMode
enum class ThreadMode {
MAIN,
BACKGROUND
}
二、封装EventBus
1.创建单例模式
使用校验锁,主要是为了线程安全,因为EventBus在各种线程总都可以进行通信
companion object {
private val instance: EventBus by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
EventBus()
}
fun getDefault() = instance
}
2.创建方法缓存池
key与value的键值对,value是方法封装的集合
private val mCacheMap: HashMap> by lazy { HashMap>() }
3.register()
防止多次注册
fun register(subscriber: Any) {
//从CacheMap中找寻 不存在那么就从subscriber中找到使用@Subscribe注解的方法
var methodSet: HashSet? = mCacheMap[subscriber]
if (methodSet == null) {
methodSet = findFromSubscriber(subscriber)
mCacheMap[subscriber] = methodSet
}
}
从Subscriber中找
不仅要在自己内部找,还要从继承的方法中找,为了减少查询的数量,每次都只找自己的方法,然后找父类的方法,添加判断是不是系统类型,类名以java. javax. andoird.开头的都是系统类型
方法体内部有且仅有一个参数,如果你想要有多个数据,可以一起封装到一个Event里面
/**
* 从Subscriber中找到方法集合
*/
private fun findFromSubscriber(subscriber: Any): HashSet {
val methodSet: HashSet = HashSet()
var clazz: Class<*>? = subscriber.javaClass
//找到自己内部是否有注册 还要找到父类是否有注册
while (clazz != null) {
//如果找到了系统级别的父类了 退出循环
if (isSystemParent(clazz.name)) {
break
}
//只是找寻当前目标的方法
val declaredMethods: Array = clazz.declaredMethods
for (method: Method in declaredMethods) {
//找到带有Subscribe注解的方法 如果没有找到 继续循环下一个
val annotation: Subscribe = method.getAnnotation(Subscribe::class.java) ?: continue
//判断有且仅有一个参数
val paramType: Array> = method.parameterTypes
if (paramType.size != 1) {
throw RuntimeException("You can only have one parameter")
}
//如果是粘性数据
if (annotation.isSticky) {
invokeSticky(method, subscriber, paramType[0], annotation.threadMode)
}
//加入集合
methodSet.add(SubscribeMethod(method, annotation.threadMode, paramType[0]))
}
//获取父类
clazz = clazz.superclass
}
return methodSet
}
4.Post
在post的时候会找到方法池中的已经存放的数据,因为需要进行线程的切换,所以需要Handler和Thread
private val mHandler: Handler by lazy { Handler() }
private val mExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() }
发送数据,通过post传入的数据类型和方法池中存在数据类型相匹配
/**
* 发送数据
*/
fun post(event: Any) {
//从CacheMap中找到对应的方法 并且调用
mCacheMap.forEach {
for (subscribeMethod in it.value) {
//判断两个对象是否相同
if (subscribeMethod.mClazz == event.javaClass) {
when (subscribeMethod.mThread) {
ThreadMode.MAIN -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
//主 -> 主
invoke(subscribeMethod.mMethod, it.key, event)
} else {
//子 -> 主
mHandler.post {
invoke(subscribeMethod.mMethod, it.key, event)
}
}
}
ThreadMode.BACKGROUND -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
//主 -> 子
mExecutor.submit {
invoke(subscribeMethod.mMethod, it.key, event)
}
} else {
//子 -> 子
invoke(subscribeMethod.mMethod, it.key, event)
}
}
}
}
}
}
}
5.解除注册
在合适的时候将方法池中的数据remove掉,在EventBus的官网中他们推荐是在onStop中进行解除,根据国情,很多手机在ActivityA跳转到ActivityB之后,ActivityA会调用onStop方法,所以很遗憾只能在onDestroy中使用。所以需要根据具体情况进行调用
/**
* 解除注册
*/
fun unRegister(subscribe: Any) {
mCacheMap.remove(subscribe)
}
6.Sticky
在上面的代码中已经把包含Sticky的代码贴上了,思路不变,只是将post和register的内容进行对调。首先需要一个实体的存储池,将postStickey的数据放到数据池中
private val mStickyMap: HashMap, Any> by lazy { HashMap, Any>() }
/**
* 传递Sticky
*/
fun postSticky(event: Any) {
mStickyMap[event.javaClass] = event
}
之后在Register中进行调用
//如果是粘性数据
if (annotation.isSticky) {
invokeSticky(method, subscriber, paramType[0], annotation.threadMode)
}
/**
* 调用Sticky方法
*/
private fun invokeSticky(
method: Method, subscribe: Any, clazz: Class<*>, threadMode: ThreadMode
) {
val data = mStickyMap[clazz] ?: return
when (threadMode) {
ThreadMode.MAIN -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
//主 -> 主
invoke(method, subscribe, data)
} else {
//子 -> 主
mHandler.post {
invoke(method, subscribe, data)
}
}
}
ThreadMode.BACKGROUND -> {
if (Looper.myLooper() == Looper.getMainLooper()) {
//主 -> 子
mExecutor.submit {
invoke(method, subscribe, data)
}
} else {
//子 -> 子
invoke(method, subscribe, data)
}
}
}
}
三、源码
最后放上源码地址 https://github.com/zmemo/EventBus