比 EventBus 更高效的事件匯流排(BusUtils)

blankj發表於2019-07-25

背景

設計這個 BusUtils 其實是在做 ApiUtils 時順手做的,因為兩者實現方式基本一致,設計前我也沒想著要和 greenrobot 的 EventBus 一較高低,但設計完總需要一個對比,所以就拿業界最優秀的事件匯流排 EventBus 比較一下吧,然後就發現我這區區 300 行不到的 BusUtils 效能比 EventBus 要高出好多,當然,這一切的前提都是在 BusUtils 是切實可用並且有效的,它也是一款執行緒安全的事件匯流排,這些我都在單測中有做過實際測試的,不吹不擂,後面我們拿資料說話,有小夥伴不相信的話也可以通過下載我的原始碼來比較即可,單測地址:BusUtilsVsEventBusTest,Android 測試地址:BusCompareActivityBusUtilsAucFrame 中的作用就是模組內傳值,其扮演的角色如下所示:

BusUtilsRole

下面介紹其使用:

使用

配置

在專案根目錄的 build.gradle 中新增 bus 外掛:

buildscript {
    dependencies {
        ...
        classpath 'com.blankj:bus-gradle-plugin:2.2'
    }
}
複製程式碼

然後在 application 模組中使用該外掛:

apply plugin: "com.blankj.bus"
複製程式碼

給你的專案新增 AndroidUtilCode 依賴:

api "com.blankj:utilcode:latest_version
複製程式碼

如果你單純只想引入 BusUtils 也是可以的,需要你自己拷貝一份這個類放到你工程裡,記得還要拷貝 ThreadUtils 哦,然後在 app 下的 build.gradle 中 配置 bus 的 DSL 域如下所示:

bus {
    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 */}

@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 即可。

規範

要想工具用得舒服,規範肯定要遵守的,所謂無規矩不成方圓,不然五花八門的問題肯定一堆堆,這裡推薦如下規範:

  • 持有事件的類和函式確保確保都是 public 的。
  • 由於 BusUtils 是用於模組內呼叫,所以可以寫一個 BusConfig 的類來儲存一個模組內所有 bus 的 Tag,方便查詢到使用方及呼叫方。
  • Tag 中最好還能帶有業務模組字尾名防止重複,是 sticky 型別的話也帶上 sticky,指定具體執行緒的話也帶上執行緒名,例如:update_avatar_sticky_main_info 這個 Tag,讓人直接望文生義。
  • 如果能結合 AucFrame 來使用,那就更規範不過了。
  • BusUtils 中事件傳輸的的 bean 都需要 keep 下來,否則開啟混淆後會找不到該實體物件而報錯。

使用已經介紹完畢,下面我們來和 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");
}
複製程式碼

通過比較如下幾點的測試來完成對比:

  • 註冊 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 次取平均值
 */
@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
複製程式碼

結論

為了方便觀察,我們生成一份圖表來比較兩者之間的效能:

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。(2.1 版本已支援 Tag 一對多及事件優先順序)

所以,BusUtilsEventBus 更友好。

  • BusUtilsEventBus 程式碼少得太多,BusUtils 的原始碼只有區區 300 行,而 EventBus 3000 行肯定是不止的哈。
  • BusUtilsEventBus 效能更好。

原理

bus 外掛原理分析

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 原理分析

接下來看下 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 中查詢是否有該 tagBusInfo,沒有的話就輸出錯誤日誌直接返回。

然後我們根據獲取到的 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);
    }
}
複製程式碼

unregisterregister 相反,就是從 mClassName_BusesMap 的 value 集合中移除,同樣需要對 mClassName_BusesMap 加鎖哦。

Change Log

打個小廣告

歡迎加入我的知識星球「基你太美」,我會在星球中分享 AucFrame 框架、大廠面經、AndroidUtilCode 更詳盡的說明...一切我所瞭解的知識,你可以通過支付進入我的星球「基你太美」進行體驗,加入後優先觀看星球中精華的部分,如果覺得星球的內容對自身沒有收益,你可以自行申請退款退出星球,也沒必要加我好友;如果你已確定要留在我的星球,可以通過掃描如下二維碼(備註:基你太美+你的星球暱稱)加我個人微信,方便我後續拉你進群(PS:進得越早價格越便宜)。

我的二維碼

相關文章