EventBus的使用及實現原理

1.EventBus的介紹與使用

1.1常見的組件間的通信方式

  • Intent:跳轉+傳參(侷限性非常大)
  • Handler:通常用來更新主線程UI,使用不當容易出現內存泄露
  • Interface:接口回調,僅限於同一線程中數據交互
  • BroadcastReceiver:有序廣播+無序廣播
  • AIDL:跨進程通信,代碼閱讀性不友好,維護成本偏高

EventBus優點

  • 代碼簡單,快
  • Jar包小,~50k
  • Activity,Fragment以及線程通信優秀
  • 穩定,在一億+應用中得到實踐

1.2EventBus定義

EventBus是一個Android優化的publish/subscribe消息事件總線,簡化了應用程序內各個組件間、組件與後臺線程間的通信。如Activites、Fragments、Threads、Services

1.3使用場景

比如網絡請求,返回時通過Handler或者BroadCastReceiver通知更新主線程UI

N個Fragment之間需要通過Listener(監聽)通信

這些需求都可以通過EventBus完成和實現。可以在任何地方發佈事件,任何地方訂閱消費事件。完美的簡化了跨組件、跨線程等方面通信的問題。

1.4官方架構圖

在這裏插入圖片描述

1.5簡單使用

  • 導入EventBus庫:implementation ‘org.greenrobot:eventbus:3.1.1’

  • 註冊和註銷

    @Override
    protected void onCreate() {
        super.onStart();
        EventBus.getDefault().register(this);
    }
    
    
    @Override
    protected void onDestroy() {
        super.onStop();
        if (EventBus.getDefault().isRegistered(this)) {
        	EventBus.getDefault().unregister(this);
        }
    }
    
  • 官方三步曲

    • 定義事件

      public class MessageEvent {
          /*Additional fields if needed*/
      }
      
    • 訂閱事件(註解+方法指定參數)

      @Subscribe(threadMode = ThreadMode.MAIN)
      public void onMessageEvent(MessageEvent event) {
          /*Do something*/
          //監聽到了發佈的事件(消息)
          //進行相關操作,比如更新UI等
      }
      
    • 發步事件(發送瀟瀟)

      EventBus.getDefault().post(new MessageEvent());
      
  • EventBus粘性事件(@Subscribe註解中加入:sticky = true 作用:延時消費或者初始化)

    @Subscribe(threadMode = ThreadMode.MAIN, sticky = true)
    public void onMessageEvent(UserInfo user) {
        Log.d(TAG, user.toString());
        /*Do something*/
        //監聽到了發佈的事件(消息)
        //進行相關操作,比如更新UI等
        //如果請求接口然後再發送事件再進行控件的更新。有時候該控件所在的頁面可能沒有初始化好。	//這時候eventbus所發送的事件就不會起作用。這時候就要用到粘性事件。粘性事件可以先發送     //事件,待接收方訂閱後接受事件。其實就是解決異步所帶來的問題
    }
    
  • 訂閱方法執行的優先級(如果兩個或多個地方訂閱同一事件,優先級高的先接收)

    @Subscribe(threadMode = ThreadMode.MAIN, priority = 1)
    public void onMessageEvent(UserInfo user) {
        //消費事件/消息
        textView.setText(user.toString());
        Log.d(TAG, "onMessageEvent");
    }
    
    //priority數值越大,優先級越高,默認都爲0
    @Subscribe(threadMode = ThreadMode.MAIN, priority = 5)
    public void onMessageEvent(UserInfo user) {
        //消費事件/消息
        textView.setText(user.toString());
        Log.d(TAG, "onMessageEvent");
    }
    

2.手寫EventBus

2.1四個類實現EventBus

  • ThreadMode:創建線程模式
  • Subscribe:創建註解
  • EventBus:封裝方法類
  • MethodManager:“存儲”方法,並通過反射進行調用
2.1.1創建線程模式
public enum ThreadMode {
    //事件的處理和事件的發送在相同的線程,所以事件處理時間不能太長,不然影響事件的發送線程,而這個線程可能是UI線程
    POSTING,

    //事件的處理在主線程中執行,事件處理不應耗時太長
    MAIN,

    //後臺進程,處理如保存數據等操作
    BACKGROUND,

    //異步執行,另起線程操作,事件處理會在單獨的線程中執行,主要用於在後臺線程中執行耗時操作
    ASYNC
}

2.1.2創建註解
@Target(ElementType.METHOD)//作用在方法之上
@Retention(RetentionPolicy.RUNTIME)//jvm在運行時通過反射獲取註解的值
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
}
2.1.3封裝方法類
/**
 * 保存符合要求訂閱方法的封裝類
 */
public class MethodManager {

    //參數類型
    private Class<?> type;
    //線程模式
    private ThreadMode threadMode;
    //執行訂閱方法
    private Method method;

    public MethodManager(Class<?> type, ThreadMode threadMode, Method method) {
        this.type = type;
        this.threadMode = threadMode;
        this.method = method;
    }


    public Class<?> getType() {
        return type;
    }

    public void setType(Class<?> type) {
        this.type = type;
    }

    public ThreadMode getThreadMode() {
        return threadMode;
    }

    public void setThreadMode(ThreadMode threadMode) {
        this.threadMode = threadMode;
    }

    public Method getMethod() {
        return method;
    }

    public void setMethod(Method method) {
        this.method = method;
    }

    @Override
    public String toString() {
        return "MethodManager{" +
                "type=" + type +
                ", threadMode=" + threadMode +
                ", method=" + method +
                '}';
    }

}
2.1.4“存儲”,方法,並通過反射進行調用
public class EventBus {

    private static volatile EventBus instance;

    //用來保存帶註解方法(滿足訂閱條件的方法)
    private Map<Object, List<MethodManager>> cacheMap;

    private EventBus() {
        cacheMap = new HashMap<>();

    }

    public static EventBus getDefault() {
        if (instance == null) {
            synchronized (EventBus.class) {
                if (instance == null) {
                    instance = new EventBus();
                }
            }
        }
        return instance;
    }

    //收到信息方法
    //找到MainActivity所有符合註解的方法
    public void register(Object getter) {
        List<MethodManager> methodList = cacheMap.get(getter);
        if (methodList == null) {
            methodList = findAnnotationMethod(getter);
            //key代表MainActivity標識,value代表MainActivity中符合註解的方法的集合
            cacheMap.put(getter, methodList);
        }
    }

    /**
     * 通過註解找到對應的方法
     * @return
     */
    private List<MethodManager> findAnnotationMethod(Object getter) {
        List<MethodManager> methodList = new ArrayList<>();
        Class<?> clazz = getter.getClass();
//        Method[] methods = clazz.getDeclaredMethods();//獲取當前類的所有方法
        Method[] methods = clazz.getMethods();//獲取當前類和父類的所有方法
        //採用第一種方案就沒有問題,採用第二種方案可以進一步優化
        while (clazz != null) {
            String clazzName = clazz.getName();
            if (clazzName.startsWith("java.") || clazzName.startsWith("javax.") || clazzName.startsWith("android.")) {
                break;
            }

            for (Method method : methods) {
                Subscribe subscribe = method.getAnnotation(Subscribe.class);
                if (subscribe == null) {
                    continue;
                }
                //校驗方法是否滿足條件
                //校驗方法返回值類型
                Type returnType = method.getGenericReturnType();
                if (!"void".equals(returnType.toString())) {
                    throw new RuntimeException(method.getName() + "方法返回值類型必須是 void");
                }
                //校驗方法參數
                Class<?>[] parameterTypes = method.getParameterTypes();
                if (parameterTypes.length != 1) {
                    throw new RuntimeException(method.getName() + "方法參數有且僅有一個");
                }
                //更多條件的校驗
                //...
                //完全符合要求,添加到集合
                MethodManager manager = new MethodManager(parameterTypes[0], subscribe.threadMode(), method);
                methodList.add(manager);
            }
            //不斷循環找出父類含有訂閱方法,直至爲空
            clazz = clazz.getSuperclass();
        }
        return methodList;
    }

    //發送操作,發佈事件
    public void post(Object setter) {
        Set<Object> set = cacheMap.keySet();
        //獲取MainActivity對象,即註冊要發佈消息的Activity或Fragment對象
        for (Object getter : set) {
            //獲取MainActivity中所有帶註解的方法
            List<MethodManager> managerList = cacheMap.get(getter);
            if (managerList != null) {
                //循環執行每個訂閱方法
                for (MethodManager manager : managerList) {
                    //isAssignableFrom(用來匹配發布者和訂閱者參數是否一致)
                    if (manager.getType().isAssignableFrom(setter.getClass())) {
                        invoke(manager, getter, setter);
                    }
                }
            }
        }
    }

    /**
    * 通過反射調用訂閱(滿足條件的帶註解)的方法
    */
    private void invoke(MethodManager manager, Object getter, Object setter) {
        Method method = manager.getMethod();
        try {
            method.invoke(getter, setter);
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }
}

demo鏈接:https://github.com/mitufengyun/EventBusDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章