⑦ 巧用运行时注解 → 只关注想关注的广播
三个要素:广播、观察者、回调方法,可以把他们都定义到一个数据类里,但遍历起来有些繁琐,直接空间换时间,加多个 map<广播类型, 观察者列表>。而同样的广播可以发送多次,用 Class类类型 表示,而观察者直接用 普通类型 表示。
考虑到:java反射方法的调用要传入对象实例 + 广播类型与Method一一对应,创建两个数据类:
class SubscriberMethod(var method: Method?, var eventType: Class<*>?) class Subscription(var subscriber: Any?, var subscriberMethod: SubscriberMethod?)
然后是一些判断、遍历逻辑,以及取消注册时的置空,比较简单,修改后的代码如下:
object Subject { private const val BRIDGE = 0x40 private const val SYNTHETIC = 0x1000 private const val MODIFIERS_IGNORE = Modifier.ABSTRACT or Modifier.STATIC or BRIDGE or SYNTHETIC // key:广播类型,value:观察者列表 private val typeMap = hashMapOf<Class<*>, ArrayList<Any>>() // key: 观察者,value:观察者数据集 private val observerMap = hashMapOf<Any, ArrayList<Subscription>>() fun register(any: Any) { try { val methods = any.javaClass.declaredMethods methods.forEach { val modifiers = it.modifiers if (modifiers and Modifier.PUBLIC != 0 && modifiers and MODIFIERS_IGNORE == 0) { val parameterTypes = it.parameterTypes if (parameterTypes.size == 1) { val subscribeAnnotation = it?.getAnnotation(Subscribe::class.java) subscribeAnnotation?.let { _ -> // 判断观察者对应的列表是否为空,空新建并添加Subscription if (observerMap[any] == null) observerMap[any] = arrayListOf() observerMap[any]!!.add(Subscription(any, SubscriberMethod(it, parameterTypes[0]))) // 判断事件对应的列表是否为空,空新建并添加观察者实例 if (typeMap[parameterTypes[0]] == null) typeMap[parameterTypes[0]] = arrayListOf() typeMap[parameterTypes[0]]!!.add(any) } } } } } catch (e: NoSuchMethodException) { e.printStackTrace() } } fun unregister(any: Any) { // 强引用,解绑时要置空 any.javaClass.let { cls -> observerMap[cls]?.forEach { it.subscriber = null it.subscriberMethod?.method = null it.subscriberMethod?.eventType = null } observerMap.remove(cls) typeMap.values.forEach { if (cls in it) { it.clear() it.remove(any.javaClass) } } } } fun postMessage(any: Any) { any.javaClass.let { cls -> if (cls in typeMap.keys) { typeMap[cls]!!.forEach { observerMap[it]?.forEach { subscription -> // 判断如果是否为要处理的Event if (subscription.subscriberMethod!!.eventType!!.isInstance(any)) { subscription.subscriberMethod!!.method!!.invoke(it, any) } } } } } } }
运行效果一致,这里我们采用反射的方式对整个观察者进行了扫描,可以是可以,但当观察者很多时,需耗费较多时间,会带来性能上的影响。有没有办法,在 运行前(编译时) 就拿到需要反射的订阅函数信息,而不用等到运行时再去遍历获取?有,那就是 编译时注解 ~
⑧ 巧用编译时注解 → 省去编译时扫描整个类
自定义一个注解处理器 → 解析 @Subscribe 类型的注解 → 生成一个包含订阅广播信息的java文件 → 广播库对这个Java类做解析获取到订阅的广播信息。
1) 自定义注解处理器
新建一个 New Module
→ Java or Kotlin Library
→ 项目名和类名都为:LBProcessor
没有合到一个项目另外建库的原因:注解处理器只在编译时运行,没必要打包到一起 ~
接着 LBProcessor类
继承 AbstractProcessor类
,定义一个用于信息打印的Messager实例,重写init()和process()方法:
@SupportedAnnotationTypes("com.example.test.temp.Subscribe") // 支持解释哪些注解类 @SupportedSourceVersion(SourceVersion.RELEASE_8) // 要支持的jdk版本 class LBProcessor: AbstractProcessor() { private var messager: Messager? = null override fun init(processingEnv: ProcessingEnvironment?) { super.init(processingEnv) messager = processingEnv?.messager messager?.printMessage(Diagnostic.Kind.WARNING, "LBProcessor init") } override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment?): Boolean { messager!!.printMessage(Diagnostic.Kind.WARNING, "LBProcessor process") return true } }
接着指定下注解处理器,按照下述路径依次创建文件夹:
在main目录下新建文件:\resources\META-INF\services\
新建文件:
javax.annotation.processing.Processor
文件内容为注解处理类的完整类名:
com.coderpig.lbprocessor.LBProcessor
也可以用AutoService 自动生成此文件,接着在app层级的build.gradle添加下述依赖:
kapt project(":LBProcessor") implementation project(':LBProcessor')
sync后,终端键入 gradlew build
看看效果:
Tips:AbstractProcessor的父类Processer会在编译阶段初始化,对当前模块内的代
码进行一次扫描,获取对应注解,然后调用process方法,根据这些注解类来做后续操作,发生在source -> complier过程:
网络异常,图片无法展示|
2) 通过注解拿到类、函数信息
一些注解相关API的使用,直接给出代码:
@SupportedAnnotationTypes("com.example.test.temp.Subscribe") // 支持解释哪些注解类 @SupportedSourceVersion(SourceVersion.RELEASE_8) // 要支持的jdk版本 class LBProcessor : AbstractProcessor() { private var messager: Messager? = null private var methodsByClass = hashMapOf<TypeElement, ArrayList<ExecutableElement>>() override fun init(processingEnv: ProcessingEnvironment?) { super.init(processingEnv) messager = processingEnv?.messager messager?.printMessage(Diagnostic.Kind.WARNING, "LBProcessor init") } override fun process( annotations: MutableSet<out TypeElement>, roundEnv: RoundEnvironment ): Boolean { annotations.forEach { val elements = roundEnv.getElementsAnnotatedWith(it) elements.forEach { element -> if (element is ExecutableElement) { // 获取类实例 val classElement = element.enclosingElement as TypeElement if(methodsByClass[classElement] == null) { methodsByClass[classElement] = arrayListOf() } methodsByClass[classElement]!!.add(element) } } } return true } }
注解处理器代码下断点进入调试模式(调试方法可看:我想调试下build.gradle),可以看到处理完后的列表:
啧啧啧,信息能拿到了,接着设计Java类~
3) 自动生成Java类的设计
从上面我们获到并能转移的信息:订阅者类的Class、方法名、广播类型的Class
需要设计传递这三者的数据结构,包两层(java实现,kt外部调可能有问题):
// 方法名 + 广播类型 public class SubscriberMethodGen { private String methodName; private Class<?> eventType; public SubscriberMethodGen(String methodName, Class<?> eventType) { this.methodName = methodName; this.eventType = eventType; } public String getMethodName() { return methodName; } public Class<?> getEventType() { return eventType; } } // 订阅者类型 + 订阅方法数组 public class SubscriberInfoGen { private Class<?> subscriberClass; private SubscriberMethodGen[] subscriberMethodGens; public SubscriberInfoGen(Class<?> subscriberClass, SubscriberMethodGen[] subscriberMethodGens) { this.subscriberClass = subscriberClass; this.subscriberMethodGens = subscriberMethodGens; } public Class<?> getSubscriberClass() { return subscriberClass; } public SubscriberMethodGen[] getSubscriberMethodGens() { return subscriberMethodGens; } }
接着是设计我们的生成类结构:
public class SubscriberGen { private static final Map<Class<?>, SubscriberInfoGen> SUBSCRIBE_INFO_MAP; static { SUBSCRIBE_INFO_MAP = new HashMap<>(); putSubscribe(new SubscriberInfoGen(com.example.test.ATestActivity.class, new SubscriberMethodGen[]{ new SubscriberMethodGen("onXXXEvent", DataEntity.class), new SubscriberMethodGen("onYYYEvent", RefreshEntity.class) })); } private static void putSubscribe(SubscriberInfoGen info) { SUBSCRIBE_INFO_MAP.put(info.getSubscriberClass(), info); } }
static静态代码块,类加载时就完成初始化,剩下生成Java时要做的操作就是:有几个订阅者塞几个putSubscribe()方法。
接着再定义一个获取订阅者方法的方法,因为这里的方法参数只是方法名,我们需要的是 Method对象,遍历反射一波,生成SubscriberMethod数组:
public SubscriberMethod[] getSubscriberMethod(Class<?> subscriberClass) { SubscriberInfoGen gen = SUBSCRIBE_INFO_MAP.get(subscriberClass); if (gen != null) { SubscriberMethodGen[] methodGens = SUBSCRIBE_INFO_MAP.get(subscriberClass).getSubscriberMethodGens(); SubscriberMethod[] methods = new SubscriberMethod[methodGens.length]; for (int i = 0; i < methodGens.length; i++) { try { Method method = subscriberClass.getDeclaredMethod(methodGens[i].getMethodName(), methodGens[i].getEventType()); methods[i] = new SubscriberMethod(method, methodGens[i].getEventType()); } catch (NoSuchMethodException e) { e.printStackTrace(); } } return methods; } return null; }
紧接着就是在我们的被订阅者类中试验能否解析成功了,改动内容如下:
private var byGen = true // 读取java文件形式的标记 private var subscriberGen: SubscriberGen? = null init { subscriberGen = SubscriberGen() // init代码块中完成初始化,避免重复创建 } fun register(any: Any) { if (byGen) { val subscriberInfo = subscriberGen!!.getSubscriberMethod((any::class.java)) subscriberInfo?.forEach { if (observerMap[any] == null) observerMap[any] = arrayListOf() observerMap[any]!!.add(Subscription(any, SubscriberMethod(it.method, it.eventType))) val eventTypeClass = it.eventType if(eventTypeClass != null) { if(typeMap[eventTypeClass] == null) typeMap[eventTypeClass] = arrayListOf() typeMap[eventTypeClass]!!.add(any) } } } else { ...原先扫描类方法的代码 }
运行后测试一波,效果一致,可以,继续往下走 ~
4) 根据注解自动生成Java类
一般方案是土方法 BufferedWriter
一行行拼接代码然后输出文件,而这里生成的文件只有一个,可以上 square/javapoet 偷下懒。导下依赖:
implementation 'com.squareup:javapoet:1.13.0'
参考API文档,对着上述的生成类,照葫芦画瓢写出生成方法,如果API不熟悉挺难搞的,直接给出完整处理代码:
// 动态生成Java文件 private fun createLBFile(className: String?) { val subscriberInfoGen = ClassName.get("com.example.test.temp", "SubscriberInfoGen") val subscriberMethod = ClassName.get("com.example.test.temp", "SubscriberMethod") val subscriberMethodGen = ClassName.get("com.example.test.temp", "SubscriberMethodGen") val classClass = ClassName.bestGuess("Class<?>") val subscriberMethodArrayClass = ClassName.bestGuess("SubscriberMethod[]") val mapClass = ClassName.bestGuess("Map<Class<?>, SubscriberInfoGen>") // 集合 val subscribeInfoMap = FieldSpec.builder(mapClass, "SUBSCRIBE_INFO_MAP") .addModifiers(Modifier.PRIVATE, Modifier.FINAL, Modifier.STATIC).build() // 为了导Map包和HashMap包引入的,没作用 val tempMap = FieldSpec.builder(Map::class.java, "tempMap") .initializer("new \$T<String, String>()", HashMap::class.java) .build() // 静态代码块部分 val staticCode = CodeBlock.builder().addStatement("SUBSCRIBE_INFO_MAP = new HashMap<>()").apply { methodsByClass.forEach { (typeElement, arrayList) -> add( "putSubscribe(new SubscriberInfoGen(\$L.class, new SubscriberMethodGen[] {\n", typeElement.qualifiedName ) arrayList.forEachIndexed { index, it -> add("new \$T(\"\$L\"", subscriberMethodGen, it.simpleName) it.parameters.forEach { param -> add(",\$L.class", param.asType()) } add(")") if (index != arrayList.size - 1) add(",") } add("\n}));\n") } }.build() // putSubscribe() 方法 val putSubscribe = MethodSpec.methodBuilder("putSubscribe") .addModifiers(Modifier.PUBLIC, Modifier.STATIC) .returns(Void.TYPE) .addParameter(subscriberInfoGen, "info") .addCode("SUBSCRIBE_INFO_MAP.put(info.getSubscriberClass(), info);") .build() // getSubscriberMethod() 方法 val getSubscriberMethod = MethodSpec.methodBuilder("getSubscriberMethod") .addModifiers(Modifier.PUBLIC) .returns(subscriberMethodArrayClass) .addParameter(classClass, "subscriberClass") .addStatement("SubscriberInfoGen gen = SUBSCRIBE_INFO_MAP.get(subscriberClass)") .addCode("if (gen != null) { \n") .addStatement("SubscriberMethodGen[] methodGens = SUBSCRIBE_INFO_MAP.get(subscriberClass).getSubscriberMethodGens()") .addStatement( "\$T[] methods = new SubscriberMethod[methodGens.length]", subscriberMethod ) .addCode("for (int i = 0; i < methodGens.length; i++) {\n") .beginControlFlow("try") .addStatement( "\$T method = subscriberClass.getDeclaredMethod(methodGens[i].getMethodName(), methodGens[i].getEventType())", Method::class.java ) .addStatement("methods[i] = new SubscriberMethod(method, methodGens[i].getEventType())") .nextControlFlow("catch (\$T e)", NoSuchMethodException::class.java) .addStatement("e.printStackTrace()") .endControlFlow() .addCode("}\n return methods;\n}\n") .addStatement("return null") .build() // 拼接生成最终类 val subscriberGen = TypeSpec.classBuilder("className") .addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addField(tempMap) .addField(subscribeInfoMap) .addStaticBlock(staticCode) .addMethod(putSubscribe) .addMethod(getSubscriberMethod) .build() try { val javaFile = JavaFile.builder("com.example.test", subscriberGen).build() javaFile.writeTo(filer) } catch (e: Exception) { print(e.message) } } // process() 方法处加上调用,后面这个Class名也可以通过扩展方式传入,如 processingEnv.options["lbClass"]: if (methodsByClass.size > 0) createLBFile("SubscriberGen")
真心写到吐血,这段玩意凑了我2个小时...接着改动下页面B的代码,也注册下事件,然后build一下:
可以,虽然没有排版,但没语法错误,直接运行,试试康效果:
⑨ 利用Handler → 解决在子线程发广播引起的问题
小伙伴一开始还觉得你很6,玩耍了一会儿后跟你说,挂了,错误日志如下:
瞅下代码:
好家伙,怎么敢的啊,直接在子线程里发送广播?em...不过好像也正常,开主线程执行某项耗时操作,完成时发个广播。
所以在postMessage()时还需要对线程进行判断,主线程直接回调,子线程则需要利用Handler消息机制。
判断是否主线程很简单,直接:
if(Looper.getMainLooper() == Looper.myLooper())
不懂原理的可以自己抠源码,也可以看我写过的:《换个姿势,带着问题看Handler》
子线程用Handler消息机制,直接借(chao)鉴(xi)广播的思路,肝出代码:
private var mHandler: Handler? = null private const val LIGHT_BROADCASTS = 666 // 广播标记 // 提供给异步广播临时用的... private var mTempEntity: Any? = null private var mTempSubscription: Subscription? = null init { subscriberGenDefault = SubscriberGen() // init代码块中完成初始化,避免重复创建 mHandler = object : Handler(Looper.getMainLooper()) { override fun handleMessage(msg: Message) { super.handleMessage(msg) when (msg.what) { LIGHT_BROADCASTS -> { if (mTempEntity != null && mTempSubscription != null) { mTempSubscription!!.subscriberMethod!!.method!!.invoke( mTempSubscription!!.subscriber, mTempEntity ) // 用完重置 mTempEntity = null mTempSubscription = null } } else -> super.handleMessage(msg) } } } } // 广播分发部分的代码 if (Looper.getMainLooper() == Looper.myLooper()) { // 直接回调 subscription.subscriberMethod!!.method!!.invoke(it, any) } else { // 非主线程,存下用到的变量,发起一个消息 mHandler!!.sendMessage(mHandler!!.obtainMessage().apply { mTempEntity = any mTempSubscription = subscription what = LIGHT_BROADCASTS }) }
虽然不优雅,但问题解决了,细心的朋友可以看到我定义了两个临时变量,也是无奈之举:
Handler传对象,对应的类需要实现序列化接口,而且有大小限制...
这里更好的解法肯定是维护一个广播消息的队列,还可以细出更多的玩法来,限于篇幅,也就到这里了~
⑩ EventBus拾遗
Ctrl+R替换全文,把"广播"都换成"EventBus",你会发现读着还挺通顺,是的,本文挂着 "实现轻量级广播" 的狗头,把 "基本的EventBus" 给肝出来了,23333。
关键的技术细节都有涉猎到,还没做的有这些:
- 设计原则 + 面向对象特性 → 对类进行细化、拆分、重新设计,尽量可扩展等;
- synchronized + 并发容器(如CopyOnWriteArrayList、ConcurrentHashMap) → 并发处理,保证线程安全;
- 设计一个广播队列 → 区分不同类型的广播,判断执行不同的处理 (源码中的postToSubscription())
- 一些其他的细节 → 日志、错误处理等。
上面这些就属于经验积累范畴的东西了,多看库源码多实践,就说这么多吧,感谢😁~