比 EventBus 更高效的事件总线(BusUtils)
背景
设计这个 BusUtils 其实是在做 ApiUtils 时顺手做的,由于两者实现方式基本一致,设计前我也没想着要和 greenrobot 的 EventBus 一较高低,但设计完总需要一个比照,所以就拿业界最优秀的事件总线 EventBus 比较一下吧,而后就发现我这区区 300 行不到的 BusUtils 性能比 EventBus 要高出好多,当然,这一切的前提都是在 BusUtils 是切实可用并且有效的,它也是一款线程安全的事件总线,这些我都在单测中有做过实际测试的,不吹不擂,后面我们拿数据说话,有小伙伴不相信的话也可以通过下载我的源码来比较就可,单测地址:BusUtilsVsEventBusTest,Android 演示网站:BusCompareActivity,BusUtils 在 AucFrame 中的作用就是板块内传值,其扮演的角色如下所示:
BusUtilsPlayer
下面详情其使用:
使用
配置
在项目根目录的 build.gradle 中增加 bus 插件:
buildscript { dependencies { ... classpath 'com.blankj:bus-gradle-plugin:2.0' }}而后在 application 板块中使用该插件:
apply plugin: "com.blankj.bus"给你的项目增加 AndroidUtilCode 依赖:
api "com.blankj:utilcode:1.25.0"假如你单纯只想引入 BusUtils 也是可以的,需要你自己拷贝一份这个类放到你工程里,记得还要拷贝 ThreadUtils 哦,而后在 app 下的 build.gradle 中 配置 bus 的 SDL 域如下所示:
api { busUtilsClass "com.xxx.xxx.BusUtils"}android { ...}可以猜测到默认的 busUtilsClass 为 com.blankj.utilcode.util.BusUtils 哈。
假如开启混淆的话还需要配置你的 BusUtils 中注解方法的防混淆,假如直接用 AndroidUtilCode 的话是不需要你配置的,我已经帮你做完了,配置你自己的 BusUtils 防混淆应该如下所示:
-keepattributes *Annotation*-keepclassmembers class * { @com.xxx.xxx.BusUtils$Bus <methods>;}当然,假如你项目是开启混淆的话,全量引入 AndroidUtilCode 也是可以的,混淆会帮你去除未使用到的类和方法。
好了,插件和依赖都配置完毕,下面详情基本使用。
基本使用
public static final String TAG_NO_PARAM = "TagNoParam";public static final String TAG_ONE_PARAM = "TagOneParam";@BusUtils.Bus(tag = TAG_NO_PARAM)public void noParamFun() {/* Do something */}@BusUtils.Bus(tag = TAG_ONE_PARAM)public void oneParamFun(String param) {/* Do something */}@Overridepublic void onStart() { super.onStart(); BusUtils.register(this);}@Overridepublic void onStop() { super.onStop(); BusUtils.unregister(this);}BusUtils.post(TAG_NO_PARAM);// noParamFun() will receiveBusUtils.post(TAG_ONE_PARAM, "param");// oneParamFun() will receive使用过 EventBus 的一定一下子就能看懂。
高级使用
粘性事件
支持粘性事件,也就是先发送,而后在订阅的时候接收到之前发送的粘性事件,把其消费掉,使用方式和 EventBus 一致,就是在 @BusUtils.Bus 注解中设置 sticky = true,具体例子如下所示:
public static final String TAG_NO_PARAM_STICKY = "TagNoParamSticky";@BusUtils.Bus(tag = TAG_NO_PARAM_STICKY, sticky = true)public void noParamStickyFun() {/* Do something */}BusUtils.postSticky(TAG_NO_PARAM_STICKY);BusUtils.register(xxx);// will invoke noParamStickyFunBusUtils.removeSticky(TAG_NO_PARAM_STICKY);// When u needn't use the sticky, remove itBusUtils.unregister(xxx);线程切换
线程切换使用的是 ThreadUtils 中的线程池,它具备安全的 Cached 线程池,以及 MAIN, IO, CPU, CACHED, SINGLE 线程池,默认不设置的话就是在提交的线程 POSTING,使用的话就是在 @BusUtils.Bus 注解中设置 threadMode = BusUtils.ThreadMode.xx 就可。
规范
要想工具用得舒服,规范一定要遵守的,所谓无规矩不成方圆,不然五花八门的问题一定一堆堆,这里推荐如下规范:
- 持有事件的类和函数确保确保都是
public的。 - 因为
BusUtils是用于板块内调用,所以可以写一个BusConfig的类来保存一个板块内所有 bus 的Tag,方便查找到使用方及调用方。 Tag中最好还能带有业务板块后缀名防止重复,是 sticky 类型的话也带上 sticky,指定具体线程的话也带上线程名,例如:update_avatar_sticky_main_info这个Tag,让人直接望文生义。- 假如能结合 AucFrame 来使用,那就更规范不过了。
- 对
BusUtils中事件传输的的bean都需要keep下来,否则开启混淆后会找不到该实体对象而报错。
使用已经详情完毕,下面我们来和 EventBus 比照下性能。
性能测试
首先,把两者的事件定义好,由于比较的是事件达到的快慢,所以内部都是空实现就可,具体代码如下所示:
@Subscribepublic void eventBusFun(String param) {}@BusUtils.Bus(tag = "busUtilsFun")public void busUtilsFun(String param) {}BusUtils 在编译时会根据 @BusUtils.Bus 注解生成一份记录 tag 和 方法签名的映射表,由于是在编译时完成的,这里我们通过反射来完成。
@Beforepublic void setUp() throws Exception { // 这一步是在 AOP 的时候注入的,这里通过反射来注入 busUtilsFun 事件,效果是一样的。 ReflectUtils getInstance = ReflectUtils.reflect(BusUtils.class).method("getInstance"); getInstance.method("registerBus", "busUtilsFun", BusUtilsVsEventBusTest.class.getName(), "busUtilsFun", String.class.getName(), "param", false, "POSTING");}通过比较如下几点的测试来完成比照:
- 注册 10000 个订阅者,共执行 10 次取平均值
- 向 1 个订阅者发送 * 1000000 次,共执行 10 次取平均值
- 向 100 个订阅者发送 * 100000 次,共执行 10 次取平均值
- 注销 10000 个订阅者,共执行 10 次取平均值
测试机器如下所示:
macOS: 2.2GHz Intel Core i7 16GB一加6: Android 9 8GB在 Android 上,我们加入 EventBus 的注解解决器来提升 EventBus 效率,让其在最优情况下和 BusUtils 比较。
接下来,我们把测试的模板代码写好,方便后续可以直接把两者比较的代码往回调中塞入就可,具体代码如下所示:
/** * @param name 传入的测试函数名 * @param sampleSize 样本的数量 * @param times 每次执行的次数 * @param callback 比较的回调函数 */private void compareWithEventBus(String name, int sampleSize, int times, CompareCallback callback) { long[][] dur = new long[2][sampleSize]; for (int i = 0; i < sampleSize; i++) { long cur = System.currentTimeMillis(); for (int j = 0; j < times; j++) { callback.runEventBus(); } dur[0][i] = System.currentTimeMillis() - cur; cur = System.currentTimeMillis(); for (int j = 0; j < times; j++) { callback.runBusUtils(); } dur[1][i] = System.currentTimeMillis() - cur; callback.restState(); } long eventBusAverageTime = 0; long busUtilsAverageTime = 0; for (int i = 0; i < sampleSize; i++) { eventBusAverageTime += dur[0][i]; busUtilsAverageTime += dur[1][i]; } System.out.println( name + "\nEventBusCostTime: " + eventBusAverageTime / sampleSize + "\nBusUtilsCostTime: " + busUtilsAverageTime / sampleSize );}public interface CompareCallback { void runEventBus(); void runBusUtils(); void restState();}下面就让我们来逐个比照测试。
注册 10000 个订阅者,共执行 10 次取平均值
/** * 注册 10000 个订阅者,共执行 10 次取平均值 */@Testpublic void compareRegister10000Times() { final List<BusUtilsVsEventBusTest> eventBusTests = new ArrayList<>(); final List<BusUtilsVsEventBusTest> busUtilsTests = new ArrayList<>(); compareWithEventBus("Register 10000 times.", 10, 10000, new CompareCallback() { @Override public void runEventBus() { BusUtilsVsEventBusTest test = new BusUtilsVsEventBusTest(); EventBus.getDefault().register(test); eventBusTests.add(test); } @Override public void runBusUtils() { BusUtilsVsEventBusTest test = new BusUtilsVsEventBusTest(); BusUtils.register(test); busUtilsTests.add(test); } @Override public void restState() { for (BusUtilsVsEventBusTest test : eventBusTests) { EventBus.getDefault().unregister(test); } eventBusTests.clear(); for (BusUtilsVsEventBusTest test : busUtilsTests) { BusUtils.unregister(test); } busUtilsTests.clear(); } });}// MacOS Output:// Register 10000 times.// EventBusCostTime: 427// BusUtilsCostTime: 41// 一加6 Output:// Register 10000 times.// EventBusCostTime: 1268// BusUtilsCostTime: 399向 1 个订阅者发送 * 1000000 次,共执行 10 次取平均值
/** * 向 1 个订阅者发送 * 1000000 次,共执行 10 次取平均值 */@Testpublic void comparePostTo1Subscriber1000000Times() { comparePostTemplate("Post to 1 subscriber 1000000 times.", 1, 1000000);}// MacOS Output:// Post to 1 subscriber 1000000 times.// EventBusCostTime: 145// BusUtilsCostTime: 33// 一加6 Output:// Post to 1 subscriber 1000000 times.// EventBusCostTime: 1247// BusUtilsCostTime: 696private void comparePostTemplate(String name, int subscribeNum, int postTimes) { final List<BusUtilsVsEventBusTest> tests = new ArrayList<>(); for (int i = 0; i < subscribeNum; i++) { BusUtilsVsEventBusTest test = new BusUtilsVsEventBusTest(); EventBus.getDefault().register(test); BusUtils.register(test); tests.add(test); } compareWithEventBus(name, 10, postTimes, new CompareCallback() { @Override public void runEventBus() { EventBus.getDefault().post("EventBus"); } @Override public void runBusUtils() { BusUtils.post("busUtilsFun", "BusUtils"); } @Override public void restState() { } }); for (BusUtilsVsEventBusTest test : tests) { EventBus.getDefault().unregister(test); BusUtils.unregister(test); }}向 100 个订阅者发送 * 10000 次,共执行 10 次取平均值
/** * 向 100 个订阅者发送 * 100000 次,共执行 10 次取平均值 */@Testpublic void comparePostTo100Subscribers10000Times() { comparePostTemplate("Post to 100 subscribers 100000 times.", 100, 100000);}// MacOS Output:// Post to 100 subscribers 100000 times.// EventBusCostTime: 139// BusUtilsCostTime: 79// 一加6 Output:// Post to 100 subscribers 100000 times.// EventBusCostTime: 3092// BusUtilsCostTime: 2900注销 10000 个订阅者,共执行 10 次取平均值
/** * 注销 10000 个订阅者,共执行 10 次取平均值 */@Testpublic void compareUnregister10000Times() { final List<BusUtilsVsEventBusTest> tests = new ArrayList<>(); for (int i = 0; i < 10000; i++) { BusUtilsVsEventBusTest test = new BusUtilsVsEventBusTest(); EventBus.getDefault().register(test); BusUtils.register(test); tests.add(test); } compareWithEventBus("Unregister 10000 times.", 10, 1, new CompareCallback() { @Override public void runEventBus() { for (BusUtilsVsEventBusTest test : tests) { EventBus.getDefault().unregister(test); } } @Override public void runBusUtils() { for (BusUtilsVsEventBusTest test : tests) { BusUtils.unregister(test); } } @Override public void restState() { for (BusUtilsVsEventBusTest test : tests) { EventBus.getDefault().register(test); BusUtils.register(test); } } }); for (BusUtilsVsEventBusTest test : tests) { EventBus.getDefault().unregister(test); BusUtils.unregister(test); }}// MacOS Output:// Unregister 10000 times.// EventBusCostTime: 231// BusUtilsCostTime: 23// 一加6 Output:// Unregister 10000 times.// EventBusCostTime: 800// BusUtilsCostTime: 199结论
为了方便观察,我们生成一份图表来比较两者之间的性能:
BusUtilsVsEventBusChart
图表中分别对四个函数在 MacOS 和 OnePlus6 中的体现进行统计,每个函数中从左向右分别是 「MacOS 的 BusUtils」、「MacOS 的 EventBus」、「OnePlus6 的 BusUtils」、「OnePlus6 的 EventBus」,可以发现,BusUtils 在注册和注销上基本比 EventBus 要快上好几倍,BusUtils 在向一些订阅者发送屡次事件比 EventBus 也快上好多,在向多个订阅者发送屡次事件也比 EventBus 快上些许。
基于以上说的这么多,假如你项目中事件总线用得比较频繁,那么可以试着用我的 BusUtils 来替代 EventBus 来提升性能,或者者在新的项目中,你也可以直接使用性能更好的 BusUtils。
下面来总结下 BusUtils 的优点:
BusUtils是通过事件Tag来确定唯一事件的,所以接收函数支持无参或者者一个参数,而EventBus只能通过 MessageEvent 来确定具体的接收者,只能接收一个参数,即使仅仅是通知,也需要定义一个 MessageEvent,所以,BusUtils传参更灵活。BusUtils在应用到项目中后,编译后便会在 application 中生成__bus__.json事件列表,如上生成的事件列表如下所示:
{ "BusUtilsClass": "com.blankj.utilcode.util.BusUtils", "rightBus": { "noParamFun": "{ desc: com.blankj.utilcode.pkg.feature.bus.BusActivity#noParamFun(), threadMode: POSTING }", "oneParamFun": "{ desc: com.blankj.utilcode.pkg.feature.bus.BusActivity#oneParamFun(java.lang.String param), threadMode: POSTING }" }, "wrongBus": {}}修改 oneParamFun 为两个参数的话,为了确保项目不会由于 BusUtils 在运行时崩溃,api 插件会使其在编译时就不过,此时 __bus__.json 文件如下所示,提醒你参数个数不对:
{ "BusUtilsClass": "com.blankj.utilcode.util.BusUtils", "rightBus": { "noParamFun": "{ desc: com.blankj.utilcode.pkg.feature.bus.BusActivity#noParamFun(), threadMode: POSTING }", }, "wrongBus": { "oneParamFun": "{ desc: com.blankj.utilcode.pkg.feature.bus.BusActivity#oneParamFun(java.lang.String param, java.lang.String param1), threadMode: POSTING, paramSize: 2 }" }同理,假如两个 bus 的 Tag 相同了,也会编译不过,提醒你项目中存在 Tag 相同的 bus。
所以,BusUtils 比 EventBus 更友好。
BusUtils比EventBus代码少得太多,BusUtils的源码只有区区 300 行,而EventBus3000 行一定是不止的哈。BusUtils比EventBus性能更好。
原理
bus 插件原理分析
bus 插件的源码在这里:bus 插件源码传送门,该插件通过 Gradle 的 transform 来完成对 BusUtils.init() 做注入,下面来一步步分析:
不明白 transform 的可以先去理解下,简单来说 transform 就是专门用来做字节码插入操作的,最常见的就是 AOP(面向切面编程),这部分我就不科普了,有兴趣的可以自己搜索理解。
说到字节码操作,那就又有知识点了,想要上手快速简单的可以使用 javassist,不过,我选择了更强大快速的 ASM,这里我就不详细详情了,有兴趣的可以自己去学习,ASM 其实也很简单的,在 ASM Bytecode Outline 这个插件帮助下写得还是很快的。
通过 ASM 扫描出所有带有 @BusUtils.Bus 注解的函数,读取并保存注解的值和函数的参数信息,相关代码如下所示:
@Overridepublic void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { className = name.replace("/", "."); super.visit(version, access, name, signature, superName, interfaces);}@Overridepublic MethodVisitor visitMethod(int access, String funName, String desc, String signature, String[] exceptions) { if (cv == null) return null; MethodVisitor mv = cv.visitMethod(access, funName, desc, signature, exceptions); busInfo = null; mv = new AdviceAdapter(Opcodes.ASM5, mv, access, funName, desc) { @Override public AnnotationVisitor visitAnnotation(String desc1, boolean visible) { final AnnotationVisitor av = super.visitAnnotation(desc1, visible); if (("L" + mBusUtilsClass + "$Bus;").equals(desc1)) { busInfo = new BusInfo(className, funName); funParamDesc = desc.substring(1, desc.indexOf(")")); return new AnnotationVisitor(Opcodes.ASM5, av) { @Override public void visit(String name, Object value) {// 可获取注解的值 super.visit(name, value); if ("tag".equals(name)) { tag = (String) value; } else if ("sticky".equals(name) && (Boolean) value) { busInfo.sticky = true; } } @Override public void visitEnum(String name, String desc, String value) { super.visitEnum(name, desc, value); if ("threadMode".equals(name)) { busInfo.threadMode = value; } } }; } return av; } @Override public void visitLocalVariable(String name, String desc, String signature, Label start, Label end, int index) { super.visitLocalVariable(name, desc, signature, start, end, index);// 获取方法参数信息 if (busInfo != null && !funParamDesc.equals("")) { if ("this".equals(name)) { return; } funParamDesc = funParamDesc.substring(desc.length());// 每次去除参数直到为 "",那么之后的就不是参数了 busInfo.paramsInfo.add(new BusInfo.ParamsInfo(Type.getType(desc).getClassName(), name)); if (busInfo.isParamSizeNoMoreThanOne && busInfo.paramsInfo.size() > 1) { busInfo.isParamSizeNoMoreThanOne = false; } } } @Override public void visitEnd() { super.visitEnd(); if (busInfo != null) { List<BusInfo> infoList = mBusMap.get(tag); if (infoList == null) { infoList = new ArrayList<>(); mBusMap.put(tag, infoList); } else if (infoList.size() == 0) { mBusMap.put(tag, infoList); } else if (infoList.size() == 1) { BusInfo info0 = infoList.get(0); info0.isTagRepeat = true; busInfo.isTagRepeat = true; } else { busInfo.isTagRepeat = true; } infoList.add(busInfo); } } }; return mv;}而后往 BusUtils.init() 插入扫描出来的内容,比方上面提到的 oneParamFun 这个函数,那么其最终插入的代码如下所示:
private void init() { this.registerBus("TagOneParam", "com.blankj.bus.BusTest", "oneParamFun", "java.lang.String", "param", false, "POSTING");}其 ASM 插入的代码如下所示:
@Overridepublic MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { if (!"init".equals(name)) { return super.visitMethod(access, name, descriptor, signature, exceptions); } // 往 init() 函数中写入 if (cv == null) return null; MethodVisitor mv = cv.visitMethod(access, name, descriptor, signature, exceptions); mv = new AdviceAdapter(Opcodes.ASM5, mv, access, name, descriptor) { @Override public AnnotationVisitor visitAnnotation(String desc, boolean visible) { return super.visitAnnotation(desc, visible); } @Override protected void onMethodEnter() { super.onMethodEnter(); } @Override protected void onMethodExit(int opcode) { super.onMethodExit(opcode); for (Map.Entry<String, List<BusInfo>> busEntry : mBusMap.entrySet()) { List<BusInfo> infoList = busEntry.getValue(); if (infoList.size() != 1) continue; BusInfo busInfo = infoList.get(0); if (!busInfo.isParamSizeNoMoreThanOne) continue; mv.visitVarInsn(ALOAD, 0); mv.visitLdcInsn(busEntry.getKey()); mv.visitLdcInsn(busInfo.className); mv.visitLdcInsn(busInfo.funName); if (busInfo.paramsInfo.size() == 1) { mv.visitLdcInsn(busInfo.paramsInfo.get(0).className); mv.visitLdcInsn(busInfo.paramsInfo.get(0).name); } else { mv.visitLdcInsn(""); mv.visitLdcInsn(""); } mv.visitInsn(busInfo.sticky ? ICONST_1 : ICONST_0); mv.visitLdcInsn(busInfo.threadMode); mv.visitMethodInsn(INVOKESPECIAL, mBusUtilsClass, "registerBus", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;ZLjava/lang/String;)V", false); } } }; return mv;}BusUtils 原理分析
接下来看下 BusUtils.registerBus 的实现:
private void registerBus(String tag, String className, String funName, String paramType, String paramName, boolean sticky, String threadMode) { mTag_BusInfoMap.put(tag, new BusInfo(className, funName, paramType, paramName, sticky, threadMode));}很简单,就是往 mTag_BusInfoMap 中插入了 key 为 tag,value 为 BusInfo 的一个实例,这样便把一个事件保留了下来。
接下来就是使用了,一开始我们都是先 register,源码如下所示:
public static void register(final Object bus) { getInstance().registerInner(bus);}private void registerInner(final Object bus) { if (bus == null) return; String className = bus.getClass().getName(); synchronized (mClassName_BusesMap) { Set<Object> buses = mClassName_BusesMap.get(className); if (buses == null) { buses = new CopyOnWriteArraySet<>(); mClassName_BusesMap.put(className, buses); } buses.add(bus); } processSticky(bus);}我们获取 bus 的类名,而后对 mClassName_BusesMap 加锁来把它插入到 mClassName_BusesMap 的 value 的集合中,可以看到我们用了线程安全的 CopyOnWriteArraySet 集合,而后还需要解决下之前能否订阅过粘性事件 processSticky,到这里 register 便结束了。
而后就是 post 来发送事件了,源码如下:
public static void post(final String tag) { post(tag, NULL);}public static void post(final String tag, final Object arg) { getInstance().postInner(tag, arg);}private void postInner(final String tag, final Object arg) { postInner(tag, arg, false);}private void postInner(final String tag, final Object arg, final boolean sticky) { BusInfo busInfo = mTag_BusInfoMap.get(tag); if (busInfo == null) { Log.e(TAG, "The bus of tag <" + tag + "> is not exists."); return; } if (busInfo.method == null) { Method method = getMethodByBusInfo(busInfo); if (method == null) { return; } busInfo.method = method; } invokeMethod(tag, arg, busInfo, sticky);}private Method getMethodByBusInfo(BusInfo busInfo) { try { if ("".equals(busInfo.paramType)) { return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName); } else { return Class.forName(busInfo.className).getDeclaredMethod(busInfo.funName, Class.forName(busInfo.paramType)); } } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (NoSuchMethodException e) { e.printStackTrace(); } return null;}private void invokeMethod(final String tag, final Object arg, final BusInfo busInfo, final boolean sticky) { Runnable runnable = new Runnable() { @Override public void run() { realInvokeMethod(tag, arg, busInfo, sticky); } }; switch (busInfo.threadMode) { case "MAIN": Utils.runOnUiThread(runnable); return; case "IO": ThreadUtils.getIoPool().execute(runnable); return; case "CPU": ThreadUtils.getCpuPool().execute(runnable); return; case "CACHED": ThreadUtils.getCachedPool().execute(runnable); return; case "SINGLE": ThreadUtils.getSinglePool().execute(runnable); return; default: runnable.run(); }}private void realInvokeMethod(final String tag, Object arg, BusInfo busInfo, boolean sticky) { Set<Object> buses = mClassName_BusesMap.get(busInfo.className); if (buses == null || buses.size() == 0) { if (!sticky) { Log.e(TAG, "The bus of tag <" + tag + "> was not registered before."); return; } else { return; } } try { if (arg == NULL) { for (Object bus : buses) { busInfo.method.invoke(bus); } } else { for (Object bus : buses) { busInfo.method.invoke(bus, arg); } } } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); }}可以看到代码还是比较多的,不过别急,我们一步步来还是很简单的,首先在我们之前注入的 mTag_BusInfoMap 中查找能否有该 tag 的 BusInfo,没有的话就输出错误日志直接返回。
而后我们根据获取到的 BusInfo 来找到 method 实例,BusInfo 第一次会把 method 保存在实例中,之后调用的话直接从实例中取出 method 就可。
接着我们从 BusInfo 中取出线程信息,最后在线程中执行 method 的反射,大体就是这样,具体细节的话还是需要自己分析源码。
最后就是 unregister 了:
public static void unregister(final Object bus) { getInstance().unregisterInner(bus);}private void unregisterInner(final Object bus) { if (bus == null) return; String className = bus.getClass().getName(); synchronized (mClassName_BusesMap) { Set<Object> buses = mClassName_BusesMap.get(className); if (buses == null || !buses.contains(bus)) { Log.e(TAG, "The bus of <" + bus + "> was not registered before."); return; } buses.remove(bus); }}unregister 和 register 相反,就是从 mClassName_BusesMap 的 value 集合中移除,同样需要对 mClassName_BusesMap 加锁哦。
1. 本站所有资源来源于用户上传和网络,如有侵权请邮件联系站长!
2. 分享目的仅供大家学习和交流,您必须在下载后24小时内删除!
3. 不得使用于非法商业用途,不得违反国家法律。否则后果自负!
4. 本站提供的源码、模板、插件等等其他资源,都不包含技术服务请大家谅解!
5. 如有链接无法下载、失效或广告,请联系管理员处理!
6. 本站资源售价只是摆设,本站源码仅提供给会员学习使用!
7. 如遇到加密压缩包,请使用360解压,如遇到无法解压的请联系管理员
开心源码网 » 比 EventBus 更高效的事件总线(BusUtils)