EventBus 我相信你們不會很默認,應該也都在項目中使用過,雖然 EventBus 在項目中不便於管理,發射出去的消息不便於跟蹤或者閱讀,可是不能否認它是一個優秀的開源項目,仍是值得咱們你們學習的,我我的感受惟一不足的就是 EventBus 功能上不能根據 TAG 來進行發射和接收消息,只能經過註解 + 消息類型進行找消息。那麼咱們本身能夠實現這個功能嗎?不能否認,固然能夠! 想要實現這個功能,咱們先大概簡要的瞭解下 EventBus 使用及源碼是怎麼實現的。java
EventBus 能夠代替 Android 傳統的 Intent, Handler, Broadcast 或接口函數, 在 Fragment, Activity, Service 線程間進行數據傳遞。git
implementation 'org.greenrobot:eventbus:3.1.1'
複製代碼
EventBus.getDefault().register(this);
複製代碼
@Subscribe(threadMode = ThreadMode.MAIN)
public void receive(String event){
Log.d(TAG,"接收到 EventBus post message:" + event);
}
複製代碼
EventBus.getDefault().post("發射一個測試消息");
複製代碼
EventBus.getDefault().unregister(this);
複製代碼
這裏就簡單介紹下 EventBus 使用,想詳細瞭解的能夠看 EventBus GitHubgithub
上面小節我們學習了 EventBus 的簡單使用,那麼咱們就根據上面使用到的來進行源碼分析。設計模式
流程圖:
緩存
register 代碼:
bash
/** * *EventBus register */
public void register(Object subscriber) {
//1. 拿到當前註冊 class
Class<?> subscriberClass = subscriber.getClass();
//2. 查找當前 class 類中全部訂閱者的方法
List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
synchronized (this) {
for (SubscriberMethod subscriberMethod : subscriberMethods) {
//3.
subscribe(subscriber, subscriberMethod);
}
}
}
複製代碼
subscribe(x,x)代碼:
架構
//必須加鎖調用
private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
//拿到訂閱者參數類型
Class<?> eventType = subscriberMethod.eventType;
Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
//根據參數類型,拿到當前全部訂閱者
CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
//4. 若是沒有拿到,則存進去緩存中
if (subscriptions == null) {
subscriptions = new CopyOnWriteArrayList<>();
subscriptionsByEventType.put(eventType, subscriptions);
} else {
//若是已經存在相同類型的註冊事件,就拋出異常
if (subscriptions.contains(newSubscription)) {
throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
+ eventType);
}
}
......
}
複製代碼
拿到當前傳入進來的 this 對象。框架
查找當前 this 類中全部訂閱的函數。異步
subscriptionsByEventType 完成數據初始化。async
subscriptionsByEventType = new HashMap();
複製代碼
// 參考上圖註釋 4 ,根據參數類型存儲訂閱者和訂閱方法
流程圖:
代碼
/** 發送事件*/
public void post(Object event) {
//在當前線程中取出變量數據
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;
}
}
}
複製代碼
private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
Class<?> eventClass = event.getClass();
boolean subscriptionFound = false;
if (eventInheritance) {
//查找全部的事件類型
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) {
logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
}
if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
eventClass != SubscriberExceptionEvent.class) {
post(new NoSubscriberEvent(this, event));
}
}
}
複製代碼
private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
CopyOnWriteArrayList<Subscription> subscriptions;
synchronized (this) {
//這裏的容器就是 register 的時候添加進行去的,如今拿出來
subscriptions = subscriptionsByEventType.get(eventClass);
}
if (subscriptions != null && !subscriptions.isEmpty()) {
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;
}
複製代碼
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
//根據訂閱者 threadMode 來進行發送
switch (subscription.subscriberMethod.threadMode) {
case POSTING:
//經過反射發送
invokeSubscriber(subscription, event);
break;
case MAIN:
//若是是主線程直接在當前線程發送
if (isMainThread) {
invokeSubscriber(subscription, event);
} else {
//開啓一個子線程發送
mainThreadPoster.enqueue(subscription, event);
}
break;
case MAIN_ORDERED:
if (mainThreadPoster != null) {
mainThreadPoster.enqueue(subscription, event);
} else {
// temporary: technically not correct as poster not decoupled from subscriber
invokeSubscriber(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);
}
}
複製代碼
步驟
這裏是訂閱者的意思,經過自定義註解實現
@Documented
@Retention(RetentionPolicy.RUNTIME) //註解會在class字節碼文件中存在,在運行時能夠經過反射獲取到
@Target({ElementType.METHOD}) //只能在方法上聲明
public @interface Subscribe {
ThreadMode threadMode() default ThreadMode.POSTING; //默認在 post 線程
boolean sticky() default false; //默認不是粘性事件
int priority() default 0; //線程優先級默認
}
複製代碼
package org.greenrobot.eventbus;
public enum ThreadMode {
POSTING, // post 線程
MAIN,//指定主線程
MAIN_ORDERED,
BACKGROUND,//後臺進行
ASYNC;//異步
private ThreadMode() {
}
}
複製代碼
流程:
到這裏咱們就簡單的分析了 註冊 - > 訂閱 - > 發送 - >接收事件 簡單的流程就是這樣了,若是想更深刻的話,建議下載源碼來看 EventBus GitHub 架構方面仍是要注重基礎知識,好比 註解 + 反射 + 設計模式 (設計模式的話建議去看 《Android 源碼設計模式》一書 ),如今開源項目幾乎離不開這幾項技術。只有咱們掌握了基礎 + 實現原理。咱們模仿着也能寫出來一樣的項目。
下面咱們就簡單模仿下 EventBus 原理,實現本身的 EventBus 框架。
說明一點,這裏咱們仍是根據 EventBus 的核心原理實現,並不會有 EventBus 那麼多功能,咱們只是學習 EventBus 原理的同時,能根據它的原理,本身寫一套輕量級的 EventBus 架構。
最後一共差很少 300 行代碼實現根據 TAG 發送/接收事件,下面是效果圖:
如下我就直接貼代碼了 每一步代碼都有詳細的註釋,相信應該不難理解。
添加依賴
allprojects {
repositories {
...
maven { url 'https://jitpack.io' }
}
}
dependencies {
implementation 'com.github.yangkun19921001:YEventBus:Tag'
}
複製代碼
註冊事件
//開始註冊事件。模仿 EventBus
YEventBus.getDefault().register(this);
複製代碼
訂閱消息
/** * 這裏是自定義的註冊,最後經過反射來獲取當前類裏面的訂閱者 * * @param meg */
@YSubscribe(threadMode = YThreadMode.MAIN, tag = Constants.TAG_1)
public void onEvent(String meg) {
Toast.makeText(getApplicationContext(), "收到:" + meg, Toast.LENGTH_SHORT).show();
}
複製代碼
發送消息
YEventBus.getDefault().post(Constants.TAG_1, "發送 TAG 爲 1 的消息");
複製代碼
/** * 註冊方法 */
public void register(Object subscriber) {
//拿到當前註冊的全部的訂閱者
List<YSubscribleMethod> ySubscribleMethods = mChacheSubscribleMethod.get(subscriber);
//若是訂閱者已經註冊了 就不須要再註冊了
if (ySubscribleMethods == null) {
//開始反射找到當前類的訂閱者
ySubscribleMethods = getSubscribleMethods(subscriber);
//註冊了就存在緩存中,避免屢次註冊
mChacheSubscribleMethod.put(subscriber, ySubscribleMethods);
}
}
/** * 拿到當前註冊的全部訂閱者 * * @param subscriber * @return */
private List<YSubscribleMethod> getSubscribleMethods(Object subscriber) {
//拿到註冊的 class
Class<?> subClass = subscriber.getClass();
//定義一個容器,用來裝訂閱者
List<YSubscribleMethod> ySubscribleMethodList = new ArrayList<>();
//開始循環找到
while (subClass != null) {
//1. 開始進行篩選,若是是系統的就不須要進行下去
String subClassName = subClass.getName();
if (subClassName.startsWith(Constants.JAVA) ||
subClassName.startsWith(Constants.JAVA_X) ||
subClassName.startsWith(Constants.ANDROID) ||
subClassName.startsWith(Constants.ANDROID_X)
) {
break;
}
//2. 遍歷拿到當前 class
Method[] declaredMethods = subClass.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
//3. 檢測當前方法中是否有 咱們的 訂閱者 註解也就是 YSubscribe
YSubscribe annotation = declaredMethod.getAnnotation(YSubscribe.class);
//若是沒有直接跳出查找
if (annotation == null)
continue;
// check 這個方法的參數是否有多個
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
if (parameterTypes.length > 1) {
throw new RuntimeException("YEventBus 只能接收一個參數");
}
//4. 符合要求,最後添加到容器中
//4.1 拿到須要在哪一個線程中接收事件
YThreadMode yThreadMode = annotation.threadMode();
//只能在當前 tag 相同下才能接收事件
String tag = annotation.tag();
YSubscribleMethod subscribleMethod = new YSubscribleMethod(tag, declaredMethod, yThreadMode, parameterTypes[0]);
ySubscribleMethodList.add(subscribleMethod);
}
//去父類找訂閱者
subClass = subClass.getSuperclass();
}
return ySubscribleMethodList;
}
複製代碼
/** * post 方法 */
public void post(String tag, Object object) {
//拿到當前全部訂閱者持有的類
Set<Object> subscriberClass = mChacheSubscribleMethod.keySet();
//拿到迭代器,
Iterator<Object> iterator = subscriberClass.iterator();
//進行循環遍歷
while (iterator.hasNext()) {
//拿到註冊 class
Object subscribleClas = iterator.next();
//獲取類中全部添加訂閱者的註解
List<YSubscribleMethod> ySubscribleMethodList = mChacheSubscribleMethod.get(subscribleClas);
for (YSubscribleMethod subscribleMethod : ySubscribleMethodList) {
//判斷這個方法是否接收事件
if (!TextUtils.isEmpty(tag) && subscribleMethod.getTag().equals(tag) //註解上面的 tag 是否跟發送者的 tag 相同,相同就接收
&& subscribleMethod.getEventType().isAssignableFrom(object.getClass() //判斷類型
)
) {
//根據註解上面的線程類型來進行切換接收消息
postMessage(subscribleClas, subscribleMethod, object);
}
}
}
}
private void postMessage(final Object subscribleClas, final YSubscribleMethod subscribleMethod, final Object message) {
//根據須要的線程來進行切換
switch (subscribleMethod.getThreadMode()) {
case MAIN:
//若是接收的是主線程,那麼直接進行反射,執行訂閱者的方法
if (isMainThread()) {
postInvoke(subscribleClas, subscribleMethod, message);
} else {//若是接收消息在主線程,發送線程在子線程那麼進行線程切換
mHandler.post(new Runnable() {
@Override
public void run() {
postInvoke(subscribleClas, subscribleMethod, message);
}
});
}
break;
case ASYNC://須要在子線程中接收
if (isMainThread())
//若是當前 post 是在主線程中,那麼切換爲子線程
ThreadUtils.executeByCached(new ThreadUtils.Task<Boolean>() {
@Nullable
@Override
public Boolean doInBackground() throws Throwable {
postInvoke(subscribleClas, subscribleMethod, message);
return true;
}
@Override
public void onSuccess(@Nullable Boolean result) {
Log.i(TAG, "執行成功");
}
@Override
public void onCancel() {
}
@Override
public void onFail(Throwable t) {
}
});
else
postInvoke(subscribleClas, subscribleMethod, message);
break;
case POSTING:
case BACKGROUND:
case MAIN_ORDERED:
postInvoke(subscribleClas, subscribleMethod, message);
break;
default:
break;
}
}
/** * 反射調用訂閱者 * * @param subscribleClas * @param subscribleMethod * @param message */
private void postInvoke(Object subscribleClas, YSubscribleMethod subscribleMethod, Object message) {
Log.i(TAG, "post message: " + "TAG:" + subscribleMethod.getTag() + " 消息體:" + message);
Method method = subscribleMethod.getMethod();
//執行
try {
method.invoke(subscribleClas, message);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
複製代碼
/** * <pre> * author : devyk on 2019-07-27 18:05 * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts * github : https://github.com/yangkun19921001 * mailbox : yang1001yk@gmail.com * desc : This is YSubscribe * </pre> */
@Target(ElementType.METHOD) //target 描述此註解在哪裏使用
@Retention(RetentionPolicy.RUNTIME) //retention 描述此註解保留的時長 這裏是在運行時
public @interface YSubscribe {
YThreadMode threadMode() default YThreadMode.POSTING; //默認是在 post 線程接收數據
String tag() default "";//根據消息來接收事件
}
複製代碼
/** * <pre> * author : devyk on 2019-07-27 18:14 * blog : https://juejin.im/user/578259398ac2470061f3a3fb/posts * github : https://github.com/yangkun19921001 * mailbox : yang1001yk@gmail.com * desc : This is YThreadMode * </pre> */
public enum YThreadMode {
/** * Subscriber will be called directly in the same thread, which is posting the event. This is the default. Event delivery * implies the least overhead because it avoids thread switching completely. Thus this is the recommended mode for * simple tasks that are known to complete in a very short time without requiring the main thread. Event handlers * using this mode must return quickly to avoid blocking the posting thread, which may be the main thread. */
POSTING,
/** * On Android, subscriber will be called in Android's main thread (UI thread). If the posting thread is * the main thread, subscriber methods will be called directly, blocking the posting thread. Otherwise the event * is queued for delivery (non-blocking). Subscribers using this mode must return quickly to avoid blocking the main thread. * If not on Android, behaves the same as {@link #POSTING}. */
MAIN,
/** * On Android, subscriber will be called in Android's main thread (UI thread). Different from {@link #MAIN}, * the event will always be queued for delivery. This ensures that the post call is non-blocking. */
MAIN_ORDERED,
/** * On Android, subscriber will be called in a background thread. If posting thread is not the main thread, subscriber methods * will be called directly in the posting thread. If the posting thread is the main thread, EventBus uses a single * background thread, that will deliver all its events sequentially. Subscribers using this mode should try to * return quickly to avoid blocking the background thread. If not on Android, always uses a background thread. */
BACKGROUND,
/** * Subscriber will be called in a separate thread. This is always independent from the posting thread and the * main thread. Posting events never wait for subscriber methods using this mode. Subscriber methods should * use this mode if their execution might take some time, e.g. for network access. Avoid triggering a large number * of long running asynchronous subscriber methods at the same time to limit the number of concurrent threads. EventBus * uses a thread pool to efficiently reuse threads from completed asynchronous subscriber notifications. */
ASYNC
}
複製代碼
/** * 取消註冊訂閱者 */
public void unRegister(Object subscriber) {
Log.i(TAG, "unRegister start:當前註冊個數" + mChacheSubscribleMethod.size());
Class<?> subClas = subscriber.getClass();
List<YSubscribleMethod> ySubscribleMethodList = mChacheSubscribleMethod.get(subClas);
if (ySubscribleMethodList != null)
mChacheSubscribleMethod.remove(subscriber);
Log.i(TAG, "unRegister success:當前註冊個數" + mChacheSubscribleMethod.size());
}
複製代碼
這個其實很簡單,拿到 post 發送的 tag,跟訂閱者的 tag 比較下就好了。其實只要瞭解原理也沒有那麼可貴。
//判斷這個方法是否接收事件
if (!TextUtils.isEmpty(tag) && subscribleMethod.getTag().equals(tag) //註解上面的 tag 是否跟發送者的 tag 相同,相同就接收
&& subscribleMethod.getEventType().isAssignableFrom(object.getClass() //判斷類型
)
複製代碼
最後咱們根據開源項目 EventBus 實現了本身 代碼傳送陣 YEventBus 框架,能夠根據 TAG 發送/接收消息。只要瞭解開源框架原理,根據本身需求改動原有框架或者實現本身的框架都不是太難,加油!