子曰:溫故而知新,可以爲師矣。 《論語》-- 孔子
一、常規的事件傳遞
Intent
意圖: 跳轉 + 傳參 (侷限性非常大)。Handler
:通常用來更新主線程 UI,使用不當容易出現內存泄漏。Interface
接口:僅限於同一線程中數據交互。BroadCastReceiver
:有序廣播 + 無序廣播。 onReceive() 方法不能超過 10 秒。AIDL
跨進程通信:代碼閱讀性不友好,維護成本偏高。- 其他方式:本地存儲…
二、EventBus 正確使用
1. 概念
- 一個 Android 優化的
publish / subscribe
消息事件總線。簡化了應用程序內各個組件間,組件與後臺線程間的通信。
2. 使用場景。
- 網絡請求,返回時通過
Handler
或者BroadCastReceiver
通知更新主線程UI
;多個Fragment
之間需要通過Listener
(監聽)通信。這些需求都可以通過EventBus
完成和實現。
3. 官方架構圖
4. 正確
使用
1)導包
// app 目錄下的 build.gradle 文件 defaultConfig 節點下加入
//給註解處理器傳參
javaCompileOptions {
annotationProcessorOptions {
arguments = [ eventBusIndex : 'com.kww.eventbusdemo.MyEventBusIndex' ]
}
}
// dependencies 節點下
implementation 'org.greenrobot:eventbus:3.2.0'
annotationProcessor 'org.greenrobot:eventbus-annotation-processor:3.2.0'
2)註冊,反註冊,訂閱事件
public class BaseApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
}
}
public class SendMessageEvent {
private String message;
public SendMessageEvent(String message) {
this.message = message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
// MainActivity
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity >>>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
EventBus.getDefault().register(this);
findViewById(R.id.btn_jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
}
@Override
protected void onDestroy() {
super.onDestroy();
if(EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().unregister(this);
}
}
@Subscribe(threadMode = ThreadMode.MAIN)
public void receiveMessage(SendMessageEvent event){
Log.e(TAG,event.getMessage());
}
}
3)發送事件
public class SecondActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
findViewById(R.id.btn_send_message).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
EventBus.getDefault().post(new SendMessageEvent("Hello"));
}
});
}
}
4)運行程序,查看 Log 日誌:
com.kww.eventbusdemo E/MainActivity >>>>: Hello
5)EventBus 的四種 ThreadMode
POSTING
(默認):如果使用事件處理函數指定了線程模型爲POSTING
,那麼該事件在哪個線程發佈出來的,事件處理函數就會在這個線程中運行,也就是說發佈事件和接收事件在同一個線程
。在線程模型爲POSTING
的事件處理函數中儘量避免執行耗時操作,因爲它會阻塞事件的傳遞,甚至有可能會引起ANR
。MAIN
:事件的處理會在UI
線程中執行。事件處理時間不能太長,否則可能會導致ANR
。BACKGROUND
:如果事件是在UI
線程中發佈出來的,那麼該事件處理函數就會在新的線程中運行,如果事件本來就是子線程中發佈出來的,那麼該事件處理函數直接在發佈事件的線程中執行。在此事件處理函數中禁止進行UI
更新操作。ASYNC
:無論事件在哪個線程發佈,該事件處理函數都會在新建的子線程
中執行,同樣,此事件處理函數中禁止進行UI
更新操作。
6)粘性事件
// MainActivity 類
findViewById(R.id.btn_jump).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// 發送粘性事件
EventBus.getDefault().postSticky(new SendMessageEvent("粘性事件"));
startActivity(new Intent(MainActivity.this,SecondActivity.class));
}
});
public class SecondActivity extends AppCompatActivity {
private static final String TAG = "SecondActivity >>>>";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
EventBus.getDefault().register(this);
@Subscribe(threadMode = ThreadMode.MAIN,sticky = true)
public void receiveMessageStick(SendMessageEvent event){
Log.e(TAG,event.getMessage());
}
@Override
protected void onDestroy() {
super.onDestroy();
if(EventBus.getDefault().isRegistered(this)){
EventBus.getDefault().unregister(this);
}
}
}
這就類似於列表與詳情頁的關係,我們的 MainActivity
就是列表頁,我們發送了一個粘性事件,相當於我們點擊某一個 item
,將當前 item
的 id
傳遞給 詳情頁去請求接口,那麼我們在 SecondActivity
,也就是 詳情頁,先收到粘性事件的 id
,然後跳轉到詳情頁面上。
7)添加混淆
-keepattributes *Annotation*
-keepclassmembers class * {
@org.greenrobot.eventbus.Subscribe <methods>;
}
-keep enum org.greenrobot.eventbus.ThreadMode { *; }
# And if you use AsyncExecutor:
-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent {
<init>(java.lang.Throwable);
}
以上就是 EventBus 3.0
之後的正確使用,可能有的人和我用的不太一樣,其實上面這中寫法纔是正確的寫法,具體的可以看官方文檔:EventBus 註解處理器使用 以及GitHub傳送門。
三、EventBus 3.0 之前源碼理解
1. EventBus.getDefault().register(this);
在這行代碼中, EventBus.getDefault()
方法獲取的是 EventBus
對象,很明顯這是一個單例模式,採用了雙重檢查模式 (DCL):
/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
EventBus instance = defaultInstance;
if (instance == null) {
synchronized (EventBus.class) {
instance = EventBus.defaultInstance;
if (instance == null) {
instance = EventBus.defaultInstance = new EventBus();
}
}
}
return instance;
}
我們看到 register()
方法的源碼:
public void register(Object subscriber) {
Class<?> subscriberClass = subscriber.getClass();
// 用 subscriberMethodFinder 提供的方法,找到在 subscriber 這個類裏面訂閱的內容。
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
subscribe(subscriber, subscriberMethod);
}
}
}
在這個方法中,它首先要拿到 List<SubscriberMethod>
這個集合,也就是傳進來的訂閱者所有的訂閱的方法,接下來遍歷訂閱者的訂閱方法來完成訂閱者的訂閱操作。對於SubscriberMethod
(訂閱方法)類中,主要就是用保存訂閱方法的 Method
對象、線程模式、事件類型、優先級、是否是粘性事件等屬性。
public class SubscriberMethod {
final Method method;
final ThreadMode threadMode;
final Class<?> eventType;
final int priority;
final boolean sticky;
/** Used for efficient comparison */
String methodString;
//......
}
好的,我們進入這個 findSubscriberMethods()
方法,看一下是如何拿到訂閱者所有方法的:
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
//從緩存中獲取SubscriberMethod集合
List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
if (subscriberMethods != null) {
return subscriberMethods;
}
//ignoreGeneratedIndex屬性表示是否忽略註解器生成的MyEventBusIndex
if (ignoreGeneratedIndex) {
//通過反射獲取subscriberMethods
subscriberMethods = findUsingReflection(subscriberClass);
} else {
subscriberMethods = findUsingInfo(subscriberClass);
}
//在獲得subscriberMethods以後,如果訂閱者中不存在@Subscribe註解並且爲public的訂閱方法,則會拋出異常。
if (subscriberMethods.isEmpty()) {
throw new EventBusException("Subscriber " + subscriberClass
+ " and its super classes have no public methods with the @Subscribe annotation");
} else {
METHOD_CACHE.put(subscriberClass, subscriberMethods);
return subscriberMethods;
}
}
首先從緩存中查找,如果找到了就立馬返回。如果緩存中沒有的話,則根據ignoreGeneratedIndex
選擇如何查找訂閱方法,該屬性默認就是false
,它可以通過內部構建者類 EventBusBuilder
設置,表示是否忽略註解器生成的 MyEventBusIndex
,這個就是採用我上面所寫的使用方式後,在編譯期 EventBus
就會自動幫我生成 MyEventBusIndex
文件。最後,找到訂閱方法後,放入緩存,以免下次繼續查找。
至於 findUsingReflection()
方法 和 findUsingReflection()
方法就不細說了,就是通過反射來獲取訂閱者中所有的方法。並依據方法的類型,參數和註解來找到根據 EventBus
指定的規則所寫的訂閱方法。
在查找完所有的訂閱方法以後便開始對所有的訂閱方法進行註冊:
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
//根據訂閱者和訂閱方法構造一個訂閱事件
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//獲取當前訂閱事件中Subscription的List集合
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//該事件對應的Subscription的List集合不存在,則重新創建並保存在subscriptionsByEventType中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//訂閱者已經註冊則拋出EventBusException
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
//遍歷訂閱事件,找到比subscriptions中訂閱事件小的位置,然後插進去
int size = subscriptions.size();
for (int i = 0; i <= size; i++) {
if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
subscriptions.add(i, newSubscription);
break;
}
}
//通過訂閱者獲取該訂閱者所訂閱事件的集合
List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
if (subscribedEvents == null) {
subscribedEvents = new ArrayList<>();
typesBySubscriber.put(subscriber, subscribedEvents);
}
//將當前的訂閱事件添加到subscribedEvents中
subscribedEvents.add(eventType);
if (subscriberMethod.sticky) {
if (eventInheritance) {
//粘性事件的處理
Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
for (Map.Entry<Class<?>, Object> entry : entries) {
Class<?> candidateEventType = entry.getKey();
if (eventType.isAssignableFrom(candidateEventType)) {
Object stickyEvent = entry.getValue();
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
} else {
Object stickyEvent = stickyEvents.get(eventType);
checkPostStickyEventToSubscription(newSubscription, stickyEvent);
}
}
}
這段源碼做了兩件事,其一就是將 訂閱方法
和 訂閱者
封裝到subscriptionsByEventType
和 typesBySubscriber
,其二就是如果是粘性事件的話,就立馬投遞、執行。
subscriptionsByEventType
:當我們 post 時,根據EventType
找到訂閱事件,從而去分發事件,處理事件的。typesBySubscriber
:調用unregister(this)
的時候,根據訂閱者找到EventType
,又根據EventType
找到訂閱事件,從而對訂閱者進行解綁。
用一張圖來總結一下 register
:
2. EventBus.getDefault().post(new SendMessageEvent("Hello"));
現在來說一下 post
發送。
/** Posts the given event to the event bus. */
public void post(Object event) {
//PostingThreadState保存着事件隊列和線程狀態信息
PostingThreadState postingState = currentPostingThreadState.get();
//獲取事件隊列,並將當前事插入到事件隊列中
List<Object> eventQueue = postingState.eventQueue;
eventQueue.add(event);
if (!postingState.isPosting) {
postingState.isMainThread = isMainThread();
postingState.isPosting = true;
if (postingState.canceled) {
throw new EventBusException("Internal error. Abort state was not reset");
}
try {
while (!eventQueue.isEmpty()) {
//處理隊列中的所有事件
postSingleEvent(eventQueue.remove(0), postingState);
}
} finally {
postingState.isPosting = false;
postingState.isMainThread = false;
}
}
}
首先從 PostingThreadState
對象中取出事件隊列,然後再將當前的事件插入到事件隊列當中。最後將隊列中的事件依次交由 postSingleEvent
方法進行處理,並移除該事件。來看看 postSingleEvent()
方法裏做了什麼:
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
//eventInheritance表示是否向上查找事件的父類,默認爲true
if (eventInheritance) {
//通過lookupAllEventTypes找到所有的父類事件並存在List中,然後通過postSingleEventForEventType方法對事件逐一處理
List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
int countTypes = eventTypes.size();
for (int h = 0; h < countTypes; h++) {
Class<?> clazz = eventTypes.get(h);
subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
}
} else {
subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
}
//找不到該事件時的異常處理
if (!subscriptionFound) {
if (logNoSubscriberMessages) {
Log.d(TAG, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
接下來看看 postSingleEventForEventType
方法:
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
//取出該事件對應的Subscription集合
synchronized (this) {
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
//將該事件的event和對應的Subscription中的信息(包擴訂閱者類和訂閱方法)傳遞給postingState
for (Subscription subscription : subscriptions) {
postingState.event = event;
postingState.subscription = subscription;
boolean aborted = false;
try {
//對事件進行處理
postToSubscription(subscription, event, postingState.isMainThread);
aborted = postingState.canceled;
} finally {
postingState.event = null;
postingState.subscription = null;
postingState.canceled = false;
}
if (aborted) {
break;
}
}
return true;
}
return false;
}
接下來看看 postToSubscription()
方法:
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
invokeSubscriber(subscription, event);
break;
case MAIN:
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
mainThreadPoster.enqueue(subscription, event);
}
break;
case BACKGROUND:
if (isMainThread) {
backgroundPoster.enqueue(subscription, event);
} else {
invokeSubscriber(subscription, event);
}
break;
case ASYNC:
asyncPoster.enqueue(subscription, event);
break;
default:
throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
}
}
取出訂閱方法的線程模式,之後根據線程模式來分別處理。舉個例子,如果線程模式是 MAIN
,提交事件的線程是主線程的話則通過反射,直接運行訂閱的方法,如果不是主線程,我們需要 mainThreadPoster
將我們的訂閱事件入隊列,mainThreadPoster
是HandlerPoster
類型的繼承自Handler
,通過Handler
將訂閱方法切換到主線程執行。
下面用一張圖來說明一下 post
流程:
3. EventBus.getDefault().unregister(this);
我們最後說一下解綁過程
public synchronized void unregister(Object subscriber) {
List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
if (subscribedTypes != null) {
for (Class<?> eventType : subscribedTypes) {
unsubscribeByEventType(subscriber, eventType);
}
typesBySubscriber.remove(subscriber);
} else {
Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
}
}
typesBySubscriber
就是在訂閱者註冊的過程中講到過這個屬性,它根據訂閱者找到 EventType
,然後根據 EventType
和訂閱者來得到訂閱事件來對訂閱者進行解綁。
四、手寫 EventBus
// Subscribe 類
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING;
}
/**
* 線程狀態
*/
public enum ThreadMode {
// 事件的處理在和事件的發送在相同的進程,所以事件處理時間不應太長,不然影響事件的發送線程,而這個線程可能是UI線程
POSTING,
// 事件的處理會在UI線程中執行,事件處理不應太長時間
MAIN,
// 後臺進程,處理如保存到數據庫等操作
BACKGROUND,
// 異步執行,另起線程操作。事件處理會在單獨的線程中執行,主要用於在後臺線程中執行耗時操作
ASYNC
}
/**
* 保存符合要求的訂閱方法封裝類
*/
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;
}
@NonNull
@Override
public String toString() {
return "MethodManager{" +
"type=" + type +
", threadMode=" + threadMode +
", method=" + method +
'}';
}
}
public class EventBus {
// volatile修飾的變量不允許線程內部緩存和重排序,即直接修改內存
private static volatile EventBus instance;
// 用來保存這些帶註解的方法(訂閱者的回調方法)
private Map<Object, List<MethodManager>> cacheMap;
private Handler handler;
private ExecutorService executorService;
private EventBus() {
cacheMap = new HashMap<>();
// Handler高級用法:將handler放在主線程使用
handler = new Handler(Looper.getMainLooper());
// 創建一個子線程(緩存線程池)
executorService = Executors.newCachedThreadPool();
}
public static EventBus getDefault() {
if (instance == null) {
synchronized (EventBus.class) {
if (instance == null) {
instance = new EventBus();
}
}
}
return instance;
}
// 找到MainActivity所有帶符合註解的方法
public void register(Object getter) {
// 獲取MainActivity所有的方法
List<MethodManager> methodList = cacheMap.get(getter);
if (methodList == null) { // 不爲空表示以前註冊完成
methodList = findAnnotationMethod(getter);
cacheMap.put(getter, methodList);
}
}
// 獲取MainActivity中所有註解的方法
private List<MethodManager> findAnnotationMethod(Object getter) {
List<MethodManager> methodList = new ArrayList<>();
// 獲取類
Class<?> clazz = getter.getClass();
// 獲取所有方法
Method[] methods = clazz.getMethods();
// 性能優化。N個父類不可能有自定義註解。排除後再反射
while (clazz != null) {
// 找出系統類,直接跳出,不添加cacheMap(因爲不是訂閱者)
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;
}
// 嚴格控制方法格式和規範
// 方法必須是返回void(一次匹配)
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(3個重要成員:方法、參數、線程)
MethodManager manager = new MethodManager(parameterTypes[0], subscribe.threadMode(), method);
methodList.add(manager);
}
// 不斷循環找出父類含有訂閱者(註解方法)的類。直到爲空,比如AppCompatActivity沒有吧
clazz = clazz.getSuperclass();
}
return methodList;
}
// SecondActivity發送消息
public void post(final Object setter) {
// 訂閱者已經登記,從登記表中找出
Set<Object> set = cacheMap.keySet();
// 比如獲取MainActivity對象
for (final Object getter : set) {
// 獲取MainActivity中所有註解的方法
List<MethodManager> methodList = cacheMap.get(getter);
if (methodList != null) {
// 循環每個方法
for (final MethodManager method : methodList) {
// 有可能多個方法的參數一樣,從而都同時收到發送的消息
// 通過EventBean來判斷是否匹配上
if (method.getType().isAssignableFrom(setter.getClass())) {
// 通過方法的類型匹配,從SecondActivity發送的EventBean對象(參數)
// 匹配MainActivity中所有註解的方法符合要求的,都發送消息
// class1.isAssignableFrom(class2) 判定此 Class 對象所表示的類或接口
// 與指定的 Class 參數所表示的類或接口是否相同,或是否是其超類或超接口
// 線程調度
switch (method.getThreadMode()) {
case POSTING:
invoke(method, getter, setter);
break;
case MAIN:
// 先判斷髮送方是否在主線程
if (Looper.myLooper() == Looper.getMainLooper()) {
invoke(method, getter, setter);
} else { // 子線程 - 主線程,切換線程(用到Handler)
handler.post(new Runnable() {
@Override
public void run() {
invoke(method, getter, setter);
}
});
}
break;
case BACKGROUND:
// 先判斷髮送方是否在主線程
if (Looper.myLooper() == Looper.getMainLooper()) {
// 主線程 - 子線程,創建一個子線程(緩存線程池)
executorService.execute(new Runnable() {
@Override
public void run() {
invoke(method, getter, setter);
}
});
} else { // 子線程 到 子線程,不用切換線程
invoke(method, getter, setter);
}
break;
}
}
}
}
}
}
// 找到匹配方法後,通過反射調用MainActivity中所有符合要求的方法
private void invoke(MethodManager method, Object getter, Object setter) {
Method execute = method.getMethod();
try {
execute.invoke(getter, setter);
} catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
寫在文末
紙上得來終覺淺,絕知此事要躬行。 《冬夜讀書示子聿》-- 陸游
至此,EventBus
的正確使用
、原理理解
以及 手寫實現
就說到這,各位看官食用愉快。