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();
}
}
}