設計這個 BusUtils 實際上是在作 ApiUtils 時順手作的,由於二者實現方式基本一致,設計前我也沒想着要和 greenrobot 的 EventBus 一較高低,但設計完總須要一個對比,因此就拿業界最優秀的事件總線 EventBus 比較一下吧,而後就發現我這區區 300 行不到的 BusUtils 性能比 EventBus 要高出好多,固然,這一切的前提都是在 BusUtils 是切實可用而且有效的,它也是一款線程安全的事件總線,這些我都在單測中有作過實際測試的,不吹不擂,後面咱們拿數聽說話,有小夥伴不相信的話也能夠經過下載個人源碼來比較便可,單測地址:BusUtilsVsEventBusTest,Android 測試地址:BusCompareActivity,BusUtils 在 AucFrame 中的做用就是模塊內傳值,其扮演的角色以下所示:java
下面介紹其使用:android
在項目根目錄的 build.gradle 中添加 bus 插件:編程
buildscript { dependencies { ... classpath 'com.blankj:bus-gradle-plugin:2.0' } }
而後在 application 模塊中使用該插件:json
apply plugin: "com.blankj.bus"
給你的項目添加 AndroidUtilCode 依賴:api
api "com.blankj:utilcode:latest_version"
若是你單純只想引入 BusUtils 也是能夠的,須要你本身拷貝一份這個類放到你工程裏,記得還要拷貝 ThreadUtils 哦,而後在 app 下的 build.gradle 中 配置 bus 的 SDL 域以下所示:安全
api { busUtilsClass "com.xxx.xxx.BusUtils" } android { ... }
能夠猜想到默認的 busUtilsClass 爲 com.blankj.utilcode.util.BusUtils 哈。若是開啓混淆的話還須要配置你的 BusUtils 中註解方法的防混淆,若是直接用 AndroidUtilCode 的話是不須要你配置的,我已經幫你作完了,配置你本身的 BusUtils 防混淆應該以下所示:app
-keepattributes *Annotation* -keepclassmembers class * { @com.xxx.xxx.BusUtils$Bus <methods>; }
固然,若是你項目是開啓混淆的話,全量引入 AndroidUtilCode 也是能夠的,混淆會幫你去除未使用到的類和方法。好了,插件和依賴都配置完畢,下面介紹基本使用。ide
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 */} @Override public void onStart() { super.onStart(); BusUtils.register(this); } @Override public void onStop() { super.onStop(); BusUtils.unregister(this); } BusUtils.post(TAG_NO_PARAM);// noParamFun() will receive BusUtils.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 noParamStickyFun BusUtils.removeSticky(TAG_NO_PARAM_STICKY);// When u needn't use the sticky, remove it BusUtils.unregister(xxx);
線程切換使用的是 ThreadUtils 中的線程池,它具備安全的 Cached 線程池,以及 MAIN, IO, CPU, CACHED, SINGLE 線程池,默認不設置的話就是在提交的線程 POSTING,使用的話就是在 @BusUtils.Bus 註解中設置 threadMode = BusUtils.ThreadMode.xx 便可。
要想工具用得舒服,規範確定要遵照的,所謂無規矩不成方圓,否則五花八門的問題確定一堆堆,這裏推薦以下規範:
使用已經介紹完畢,下面咱們來和 EventBus 對比下性能。
首先,把二者的事件定義好,由於比較的是事件達到的快慢,因此內部都是空實現便可,具體代碼以下所示:
@Subscribe public void eventBusFun(String param) { } @BusUtils.Bus(tag = "busUtilsFun") public void busUtilsFun(String param) { }
BusUtils 在編譯時會根據 @BusUtils.Bus 註解生成一份記錄 tag 和 方法簽名的映射表,由於是在編譯時完成的,這裏咱們經過反射來完成。
@Before public 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"); }
經過比較以下幾點的測試來完成對比:
測試機器以下所示:
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 次取平均值 */ @Test public 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 次取平均值 */ @Test public 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: 696 private 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 個訂閱者發送 * 100000 次,共執行 10 次取平均值
/** * 向 100 個訂閱者發送 * 100000 次,共執行 10 次取平均值 */ @Test public 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 次取平均值 */ @Test public 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
爲了方便觀察,咱們生成一份圖表來比較二者之間的性能:
圖表中分別對四個函數在 MacOS 和 OnePlus6 中的表現進行統計,每一個函數中從左向右分別是 「MacOS 的 BusUtils」、「MacOS 的 EventBus」、「OnePlus6 的 BusUtils」、「OnePlus6 的 EventBus」,能夠發現,BusUtils 在註冊和註銷上基本比 EventBus 要快上好幾倍,BusUtils 在向少許訂閱者發送屢次事件比 EventBus 也快上好多,在向多個訂閱者發送屢次事件也比 EventBus 快上些許。基於以上說的這麼多,若是你項目中事件總線用得比較頻繁,那麼能夠試着用個人 BusUtils 來替代 EventBus 來提高性能,或者在新的項目中,你也能夠直接使用性能更好的 BusUtils。下面來總結下 BusUtils 的優勢:
{ "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 更友好。
bus 插件的源碼在這裏:bus 插件源碼傳送門,該插件經過 Gradle 的 transform 來完成對 BusUtils.init() 作注入,下面來一步步分析:不明白 transform 的能夠先去了解下,簡單來講 transform 就是專門用來作字節碼插入操做的,最多見的就是 AOP(面向切面編程),這部分我就不科普了,有興趣的能夠本身搜索瞭解。說到字節碼操做,那就又有知識點了,想要上手快速簡單的可使用 javassist,不過,我選擇了更強大快速的 ASM,這裏我就不詳細介紹了,有興趣的能夠本身去學習,ASM 其實也很簡單的,在 ASM Bytecode Outline 這個插件幫助下寫得仍是很快的。經過 ASM 掃描出全部帶有 @BusUtils.Bus 註解的函數,讀取並保存註解的值和函數的參數信息,相關代碼以下所示:
@Override public 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); } @Override public 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 插入的代碼以下所示:
@Override public 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.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 加鎖哦。