【EventBus】事件通信框架 ( 总结 | 手写事件通信框架完整代码示例 | 测试上述框架 )
文章目錄
- 一、消息中心
- 二、訂閱方法時的注解
- 三、訂閱方法封裝
- 四、訂閱對象-方法封裝
- 五、線程模式
- 六、Activity 中測試上述框架
- 七、博客源碼
一、消息中心
該消息中心是事件通信框架的核心代碼 , 負責訂閱方法的注冊 , 消息事件轉發 , 訂閱方法取消注冊操作 ;
package com.eventbus_demo.myeventbus;import android.os.Handler; import android.os.Looper;import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class MyEventBus {/*** 方法緩存* Key - 訂閱類類型* Value - 訂閱方法 MySubscriberMethod 的集合* 取名與 EventBus 一致*/private static final Map<Class<?>, List<MySubscriberMethod>> METHOD_CACHE = new HashMap<>();/*** 解除注冊時使用* Key - 訂閱者對象* Value - 訂閱者對象中所有的訂閱方法的事件參數類型集合** 根據該訂閱者對象 , 查找所有訂閱方法的事件參數類型 , 然后再到 METHOD_CACHE 中 ,* 根據事件參數類型 , 查找對應的 MySubscriberMethod 集合* MySubscriberMethod 中封裝 訂閱者對象 + 訂閱方法**/private final Map<Object, List<Class<?>>> typesBySubscriber;/*** Key - 訂閱者方法事件參數類型* Value - 封裝 訂閱者對象 與 訂閱方法 的 MySubscription 集合* 在構造函數中初始化* CopyOnWriteArrayList 在寫入數據時會拷貝一個副本 ,* 寫完之后 , 將引用指向新的副本 ,* 該集合的線程安全級別很高*/private final Map<Class<?>, CopyOnWriteArrayList<MySubscription>> subscriptionsByEventType;/*** 線程池*/private final ExecutorService executorService;/*** 全局單例*/private static MyEventBus instance;private MyEventBus() {subscriptionsByEventType = new HashMap<>();typesBySubscriber = new HashMap<>();executorService = Executors.newCachedThreadPool();}public static MyEventBus getInstance() {if (instance == null) {instance = new MyEventBus();}return instance;}/*** 注冊訂閱者* @param subscriber*/public void register(Object subscriber) {// 獲取訂閱者所屬類Class<?> clazz = subscriber.getClass();// 查找訂閱方法List<MySubscriberMethod> subscriberMethods = findSubscriberMethods(clazz);// 遍歷所有訂閱方法 , 進行訂閱// 首先確保查找到的訂閱方法不為空 , 并且個數大于等于 1 個if (subscriberMethods != null && !subscriberMethods.isEmpty()) {for (MySubscriberMethod method : subscriberMethods) {// 正式進行訂閱subscribe(subscriber, method);}}}/*** 方法訂閱* 將 訂閱方法參數類型 和 訂閱類 + 訂閱方法 封裝類 , 保存到* Map<Class<?>, CopyOnWriteArrayList<MySubscription>> subscriptionsByEventType 集合中* Key - 訂閱者方法事件參數類型* Value - 封裝 訂閱者對象 與 訂閱方法 的 MySubscription 集合** 取消注冊數據準備* 取消注冊數據存放在 Map<Object, List<Class<?>>> typesBySubscriber 集合中* Key - 訂閱者對象* Value - 訂閱者方法參數集合** @param subscriber 訂閱者對象* @param subscriberMethod 訂閱方法*/private void subscribe(Object subscriber, MySubscriberMethod subscriberMethod) {// 獲取訂閱方法接收的參數類型Class<?> eventType = subscriberMethod.getEventType();// 獲取 eventType 參數類型對應的 訂閱者封裝類 ( 封裝 訂閱者對象 + 訂閱方法 ) 集合CopyOnWriteArrayList<MySubscription> subscriptions =subscriptionsByEventType.get(eventType);// 如果獲取的集合為空 , 說明 eventType 參數對應的訂閱方法一個也沒有注冊過// 這里先創建一個集合 , 放到 subscriptionsByEventType 鍵值對中if (subscriptions == null) {// 創建集合subscriptions = new CopyOnWriteArrayList<>();// 將集合設置到 subscriptionsByEventType 鍵值對集合中subscriptionsByEventType.put(eventType, subscriptions);}// 封裝 訂閱者對象 + 訂閱方法 對象MySubscription subscription = new MySubscription(subscriber, subscriberMethod);// 將創建的 訂閱者對象 + 訂閱方法 對象 添加到 CopyOnWriteArrayList 集合中subscriptions.add(subscription);// 為取消注冊準備數據// 設置 Map<Object, List<Class<?>>> typesBySubscriberList<Class<?>> eventTypes = typesBySubscriber.get(subscriber);if (eventTypes == null) {// 創建新的集合, 用于存放訂閱方法的參數類型eventTypes = new ArrayList<>();// 將新的集合設置到 Map<Object, List<Class<?>>> typesBySubscriber 集合中typesBySubscriber.put(subscriber, eventTypes);}// 將新的 訂閱方法類型 放入到集合中eventTypes.add(eventType);}/*** 根據訂閱方法的事件參數查找訂閱方法* @param subscriberClass 訂閱者對象的類型* @return*/private List<MySubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {// 獲取 Class<?> clazz 參數類型對應的 訂閱者封裝類List<MySubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);// 此處后期重構, 減少縮進if (subscriberMethods == null) {// 說明是首次獲取 , 初始化 METHOD_CACHE 緩存// 反射獲取 Class<?> subscriberClass 中的所有訂閱方法subscriberMethods = findByReflection(subscriberClass);if (! subscriberMethods.isEmpty()) {METHOD_CACHE.put(subscriberClass, subscriberMethods);}} else {// 如果當前不是第一次獲取, 則直接返回從 METHOD_CACHE 緩存中獲取的 訂閱者封裝類 集合return subscriberMethods;}// 該分支走不到return subscriberMethods;}/*** 通過反射獲取 Class<?> subscriberClass 訂閱方法* @param subscriberClass 訂閱類* @return*/private List<MySubscriberMethod> findByReflection(Class<?> subscriberClass) {// 要返回的 MySubscriberMethod 集合List<MySubscriberMethod> subscriberMethods = new ArrayList<>();// 通過反射獲取所有帶 @MySubscribe 注解的方法Method[] methods = subscriberClass.getMethods();// 遍歷所有的方法 , 查找注解for (Method method : methods) {// 獲取方法修飾符int modifiers = method.getModifiers();// 獲取方法參數Class<?>[] params = method.getParameterTypes();// 確保修飾符必須是 public , 參數長度必須是 1if (modifiers == Modifier.PUBLIC && params.length == 1) {// 獲取 MySubscribe 注解MySubscribe annotation = method.getAnnotation(MySubscribe.class);// 獲取注解不為空if (annotation != null) {// 獲取線程模式MyThreadMode threadMode = annotation.threadMode();// 此時已經完全確定該方法是一個訂閱方法 , 直接進行封裝MySubscriberMethod subscriberMethod = new MySubscriberMethod(method, // 方法對象threadMode, // 線程模式params[0] // 事件參數);// 加入到返回集合中subscriberMethods.add(subscriberMethod);}}}return subscriberMethods;}/*** 接收到了 發布者 Publisher 發送給本消息中心 的 Event 消息事件對象* 將該事件對象轉發給相應接收該類型消息的 訂閱者 ( 訂閱對象 + 訂閱方法 )* 通過事件類型到* Map<Class<?>, CopyOnWriteArrayList<MySubscription>> subscriptionsByEventType* 集合中查找相應的 訂閱對象 + 訂閱方法* @param event*/public void post(Object event) {// 獲取事件類型Class<?> eventType = event.getClass();// 獲取事件類型對應的 訂閱者 集合CopyOnWriteArrayList<MySubscription> subscriptions =subscriptionsByEventType.get(eventType);// 確保訂閱者大于等于 1 個if (subscriptions != null && subscriptions.size() > 0) {// 遍歷訂閱者并調用訂閱方法for (MySubscription subscription : subscriptions) {postSingleSubscription(subscription, event);}}}/*** 調用訂閱方法* @param subscription* @param event*/private void postSingleSubscription(MySubscription subscription, Object event) {// 判斷當前線程是否是主線程// 獲取 mainLooper 與 myLooper 進行比較 , 如果一致 , 說明該線程是主線程boolean isMainThread = false;// 下面的情況下 , 線程是主線程if (Looper.getMainLooper() == Looper.myLooper()) {isMainThread = true;}// 判斷訂閱方法的線程模式MyThreadMode threadMode = subscription.getSubscriberMethod().getThreadMode();switch (threadMode) {case POSTING:// 直接在發布線程調用訂閱方法invokeMethod(subscription, event);break;case MAIN:case MAIN_ORDERED:// 如果發布線程是主線程, 直接調用if (isMainThread) {invokeMethod(subscription, event);} else {// 將訂閱方法放到主線程執行// 獲取主線程 Looper , 并通過 Looper 創建 HandlerHandler handler = new Handler(Looper.getMainLooper());// 在主線程中執行訂閱方法handler.post(new Runnable() {@Overridepublic void run() {invokeMethod(subscription, event);}});}break;case BACKGROUND:case ASYNC:// 如果是主線程 , 切換到子線程執行if (isMainThread) {// 在線程池中執行方法executorService.execute(new Runnable() {@Overridepublic void run() {invokeMethod(subscription, event);}});} else {// 如果是子線程直接執行invokeMethod(subscription, event);}break;}}/*** 調用訂閱者的訂閱方法* @param subscription 訂閱者對象 + 訂閱方法* @param event 發布者傳遞的消息事件*/private void invokeMethod(MySubscription subscription, Object event) {try {// 通過反射調用訂閱方法subscription.getSubscriberMethod().getMethod().invoke(subscription.getSubscriber(), // 訂閱者對象event // 事件參數類型);} catch (IllegalAccessException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();}}/*** 取消注冊* 從 Map<Object, List<Class<?>>> typesBySubscriber 集合中獲取* 訂閱者對象 中的 訂閱方法 參數集合** 然后再到* Map<Class<?>, CopyOnWriteArrayList<MySubscription>> subscriptionsByEventType* 集合中獲取 訂閱方法參數類型 對應的 CopyOnWriteArrayList<MySubscription>> 集合* MySubscription 中封裝了 訂閱者對象 + 訂閱方法* @param subscriber*/public void unregister(Object subscriber) {// 首先獲取 訂閱者 對象中的訂閱方法的參數集合List<Class<?>> types = typesBySubscriber.get(subscriber);// 遍歷參數類型for (Class<?> type: types) {// 獲取 接收 type 事件類型的 訂閱者集合// MySubscription 中封裝了訂閱者對象 + 訂閱方法CopyOnWriteArrayList<MySubscription> subscriptions =subscriptionsByEventType.get(type);// 判定 CopyOnWriteArrayList<MySubscription> 集合中的 MySubscription 元素// 如果如果 封裝類對象 中的 訂閱者對象 與 本次取消注冊的訂閱者對象相同 , 則從集合中移除該訂閱者// 記錄集合大小int subscriptionsSize = subscriptions.size();for (int i = 0; i < subscriptionsSize; i++) {// 獲取 訂閱者對象 + 訂閱方法 封裝類 對象MySubscription subscription = subscriptions.get(i);// 如果 封裝類對象 中的 訂閱者對象 與 本次取消注冊的訂閱者對象相同// 將其從該集合中刪除if (subscription.getSubscriber() == subscriber) {// 刪除 i 索引元素subscriptions.remove(i);// 應用新的集合大小 , 集合少了一個元素subscriptionsSize--;// 第 i 個元素被刪除了 , 之后會自增遍歷下一個元素// 下一次遍歷的還是第 i 個元素// 由于后面循環操作需要自增 , 想要之后仍然遍歷第 i 個元素 ,// 這里對 i 進行自減操作i--;}}// 刪除了訂閱者 , 就完成了取消注冊操作}} }二、訂閱方法時的注解
定義一個注解 , 該注解用于修飾方法 ElementType.METHOD , 在運行時 , 用戶調用 register 注冊訂閱者時 , 會分析哪個方法中存在該注解 , 將有注解的方法保存起來 , 以便之后的調用 ;
該注解需要保存到運行時 RetentionPolicy.RUNTIME ;
package com.eventbus_demo.myeventbus;import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;@Retention(RetentionPolicy.RUNTIME) // 該注解保留到運行時 @Target(ElementType.METHOD) // 該注解作用于方法 public @interface MySubscribe {/*** 注解屬性, 設置線程模式, 默認是 POSTING,* 即在發布線程調用訂閱方法* @return*/MyThreadMode threadMode() default MyThreadMode.POSTING; }三、訂閱方法封裝
將 訂閱方法 , 訂閱方法的線程模式 , 訂閱方法接收的事件類型 , 封裝到類中 ;
package com.eventbus_demo.myeventbus;import java.lang.reflect.Method;/*** 該類中用于保存訂閱方法相關信息*/ public class MySubscriberMethod {/*** 訂閱方法*/private final Method method;/*** 訂閱方法的線程模式*/private final MyThreadMode threadMode;/*** 訂閱方法接收的事件類型*/private final Class<?> eventType;public MySubscriberMethod(Method method, MyThreadMode threadMode, Class<?> eventType) {this.method = method;this.threadMode = threadMode;this.eventType = eventType;}public Method getMethod() {return method;}public MyThreadMode getThreadMode() {return threadMode;}public Class<?> getEventType() {return eventType;} }四、訂閱對象-方法封裝
再次進行封裝 , 將 訂閱者對象 和 訂閱方法 , 封裝到一個類中 , 這個類對象是 注冊 , 取消注冊 , 事件調用 操作的基本單元 ;
獲取到該類的對象 , 就可以執行訂閱方法 ;
package com.eventbus_demo.myeventbus;/*** 封裝 訂閱者對象 與 訂閱方法*/ public class MySubscription {/*** 訂閱者對象*/private final Object subscriber;/*** 訂閱方法*/private final MySubscriberMethod subscriberMethod;public MySubscription(Object subscriber, MySubscriberMethod subscriberMethod) {this.subscriber = subscriber;this.subscriberMethod = subscriberMethod;}public Object getSubscriber() {return subscriber;}public MySubscriberMethod getSubscriberMethod() {return subscriberMethod;} }五、線程模式
仿照 EventBus 的線程模式 , 直接照搬過來 ;
線程模式用法參考 【EventBus】Subscribe 注解分析 ( Subscribe 注解屬性 | threadMode 線程模型 | POSTING | MAIN | MAIN_ORDERED | ASYNC) ;
package com.eventbus_demo.myeventbus;/*** 直接使用 EventBus 中的現成模式*/ public enum MyThreadMode {POSTING,MAIN,MAIN_ORDERED,BACKGROUND,ASYNC }六、Activity 中測試上述框架
在 Activity 中使用 @MySubscribe 注解修飾處理消息的方法 , 該方法必須是 public void 修飾的 , 只有一個參數 , 參數類型隨意 , 調用 MyEventBus.getInstance().post 方法即可發送消息到該方法進行處理 ;
/*** 使用 @MySubscribe 注解修飾處理消息的方法* 該方法必須是 public void 修飾的* 只有一個參數 , 參數類型隨意* 調用 MyEventBus.getInstance().post 即可發送消息到該方法進行處理* @param msg*/@MySubscribepublic void onMessgeEvent(String msg){textView.setText(msg);}在 onCreate 方法中 , 注冊訂閱方法 ;
@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);// 首先注冊訂閱 EventBusMyEventBus.getInstance().register(this);}點擊按鈕 , 發送消息事件 , 這里發送了一個字符串 ;
textView = findViewById(R.id.textView);// 設置點擊事件, 點擊后發送消息textView.setOnClickListener((View view)->{MyEventBus.getInstance().post("Hello EventBus !");});完整 Activity 代碼 :
package com.eventbus_demo;import android.os.Bundle; import android.view.View; import android.widget.TextView;import androidx.appcompat.app.AppCompatActivity;import com.eventbus_demo.myeventbus.MyEventBus; import com.eventbus_demo.myeventbus.MySubscribe;import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.Subscribe; import org.greenrobot.eventbus.ThreadMode;/*** 使用 MyEventBus 依賴庫*/ public class MainActivity3 extends AppCompatActivity {private TextView textView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = findViewById(R.id.textView);// 設置點擊事件, 點擊后發送消息textView.setOnClickListener((View view)->{MyEventBus.getInstance().post("Hello EventBus !");});// 首先注冊訂閱 EventBusMyEventBus.getInstance().register(this);}/*** 使用 @Subscribe 注解修飾處理消息的方法* 該方法必須是 public void 修飾的* 只有一個參數 , 參數類型隨意* 調用 EventBus.getDefault().post 即可發送消息到該方法進行處理* @param msg*/@MySubscribepublic void onMessgeEvent(String msg){textView.setText(msg);}@Overrideprotected void onDestroy() {super.onDestroy();// 取消注冊MyEventBus.getInstance().unregister(this);} }執行效果 : 點擊后 , 頁面變為如下效果 ;
七、博客源碼
GitHub : https://github.com/han1202012/EventBus_Demo
《新程序員》:云原生和全面數字化實踐50位技術專家共同創作,文字、視頻、音頻交互閱讀總結
以上是生活随笔為你收集整理的【EventBus】事件通信框架 ( 总结 | 手写事件通信框架完整代码示例 | 测试上述框架 )的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【EventBus】事件通信框架 ( 取
- 下一篇: 【音频处理】Melodyne 自动修正功