怎麼GET大牛的框架思維?透過EventBus框架告訴你
EventBus的作用
Android中存在各種通訊場景,如
Activity
之間的跳轉,
Activity
與
Fragment
以及其他元件之間的互動,以及在某個耗時操作(如請求網路)之後的callback回撥等,互相之之間往往需要持有對方的引用,每個場景的寫法也有差異,導致耦合性較高且不便維護。
以
Activity
和
Fragment
的通訊為例,官方做法是實現一個介面,然後持有對方的引用,再強行轉成介面型別,導致耦合度偏高。
再以
Activity
的返回為例,一方需要設定
setResult
,而另一方需要在
onActivityResult
做對應處理,如果有多個返回路徑,程式碼就會十分臃腫。而
SimpleEventBus
(本文最終實現的簡化版事件匯流排)的寫法如下:
<pre class="juejin-editor-highlight" style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.8em; position: relative; padding: 0.5em 1em; background: rgb(248, 248, 248); overflow: auto; border-radius: 2px; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class MainActivity extends AppCompatActivity { TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.tv_demo); mTextView.setText("MainActivity"); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Intent intent = new Intent(MainActivity.this, SecondActivity.class); startActivity(intent); } }); EventBus.getDefault().register(this); } @Subscribe(threadMode = ThreadMode.MAIN) public void onReturn(Message message) { mTextView.setText(message.mContent); } @Override protected void onDestroy() { super.onDestroy(); EventBus.getDefault().unregister(this); } }
來源
Activity
:
<pre class="juejin-editor-highlight" style="box-sizing: border-box; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; font-size: 0.8em; position: relative; padding: 0.5em 1em; background: rgb(248, 248, 248); overflow: auto; border-radius: 2px; color: rgb(51, 51, 51); font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial;">public class SecondActivity extends AppCompatActivity { TextView mTextView; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = findViewById(R.id.tv_demo); mTextView.setText("SecondActivity,點選返回"); mTextView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { Message message = new Message(); message.mContent = "從SecondActivity返回"; EventBus.getDefault().post(message); finish(); } }); } }
效果如下:
似乎只是換了一種寫法,但在場景愈加複雜後,
EventBus
能夠體現出更好的解耦能力
背景知識
主要涉及三方面的知識:
-
觀察者模式(or 釋出-訂閱模式)
-
Android訊息機制
-
Java併發程式設計
實現過程
EventBus`的使用分三個步驟:註冊監聽、傳送事件和取消監聽,相應本文也將分這三步來實現。
註冊監聽
定義一個註解:
@Retention(RetentionPolicy.RUNTIME)@Target(ElementType.METHOD)public @interface Subscribe { ThreadMode threadMode() default ThreadMode.POST; }
greenrobot/EventBus
還支援優先順序和粘性事件,這裡只支援最基本的能力:區分執行緒,因為如更新UI的操作必須放在主執行緒。
ThreadMode`如下:
public enum ThreadMode { MAIN, // 主執行緒 POST, // 傳送訊息的執行緒 ASYNC // 新開一個執行緒傳送}
在物件初始化的時候,使用
register
方法註冊,該方法會解析被註冊物件的所有方法,並解析宣告瞭註解的方法(即觀察者),核心程式碼如下:
public class EventBus { ... public void register(Object subscriber) { if (subscriber == null) { return; } synchronized (this) { subscribe(subscriber); } } ... private void subscribe(Object subscriber) { if (subscriber == null) { return; } // TODO 巨踏馬難看的縮排 Class<?> clazz = subscriber.getClass(); while (clazz != null && !isSystemClass(clazz.getName())) { final Method[] methods = clazz.getDeclaredMethods(); for (Method method : methods) { Subscribe annotation = method.getAnnotation(Subscribe.class); if (annotation != null) { Class<?>[] paramClassArray = method.getParameterTypes(); if (paramClassArray != null && paramClassArray.length == 1) { Class<?> paramType = convertType(paramClassArray[0]); EventType eventType = new EventType(paramType); SubscriberMethod subscriberMethod = new SubscriberMethod(method, annotation.threadMode(), paramType); realSubscribe(subscriber, subscriberMethod, eventType); } } } clazz = clazz.getSuperclass(); } } ... private void realSubscribe(Object subscriber, SubscriberMethod method, EventType eventType) { CopyOnWriteArrayList<Subscription> subscriptions = mSubscriptionsByEventtype.get(subscriber); if (subscriptions == null) { subscriptions = new CopyOnWriteArrayList<>(); } Subscription subscription = new Subscription(subscriber, method); if (subscriptions.contains(subscription)) { return; } subscriptions.add(subscription); mSubscriptionsByEventtype.put(eventType, subscriptions); } ... }
執行過這些邏輯後,該物件所有的觀察者方法都會被存在一個Map中,其Key是
EventType
,即觀察事件的型別,Value是訂閱了該型別事件的所有方法(即觀察者)的一個列表,每個方法和物件一起封裝成了一個
Subscription
類:
public class Subscription { public final Reference<Object> subscriber; public final SubscriberMethod subscriberMethod; public Subscription(Object subscriber, SubscriberMethod subscriberMethod) { this.subscriber = new WeakReference<>(subscriber);// EventBus3 沒用弱引用? this.subscriberMethod = subscriberMethod; } @Override public int hashCode() { return subscriber.hashCode() + subscriberMethod.methodString.hashCode(); } @Override public boolean equals(Object obj) { if (obj instanceof Subscription) { Subscription other = (Subscription) obj; return subscriber == other.subscribe && subscriberMethod.equals(other.subscriberMethod); } else { return false; } } }
如此,便是註冊監聽方法的核心邏輯了。
訊息傳送
訊息的傳送程式碼很簡單:
public class EventBus { ... private EventDispatcher mEventDispatcher = new EventDispatcher(); private ThreadLocal<Queue<EventType>> mThreadLocalEvents = new ThreadLocal<Queue<EventType>>() { @Override protected Queue<EventType> initialValue() { return new ConcurrentLinkedQueue<>(); } }; ... public void post(Object message) { if (message == null) { return; } mThreadLocalEvents.get().offer(new EventType(message.getClass())); mEventDispatcher.dispatchEvents(message); } ... }
比較複雜一點的是需要根據註解宣告的執行緒模式在對應的執行緒進行釋出:
public class EventBus { ... private class EventDispatcher { private IEventHandler mMainEventHandler = new MainEventHandler(); private IEventHandler mPostEventHandler = new DefaultEventHandler(); private IEventHandler mAsyncEventHandler = new AsyncEventHandler(); void dispatchEvents(Object message) { Queue<EventType> eventQueue = mThreadLocalEvents.get(); while (eventQueue.size() > 0) { handleEvent(eventQueue.poll(), message); } } private void handleEvent(EventType eventType, Object message) { List<Subscription> subscriptions = mSubscriptionsByEventtype.get(eventType); if (subscriptions == null) { return; } for (Subscription subscription : subscriptions) { IEventHandler eventHandler = getEventHandler(subscription.subscriberMethod.threadMode); eventHandler.handleEvent(subscription, message); } } private IEventHandler getEventHandler(ThreadMode mode) { if (mode == ThreadMode.ASYNC) { return mAsyncEventHandler; } if (mode == ThreadMode.POST) { return mPostEventHandler; } return mMainEventHandler; } }// end of the class ... }
三種執行緒模式分別如下,DefaultEventHandler(在釋出執行緒執行觀察者放方法):
public class DefaultEventHandler implements IEventHandler { @Override public void handleEvent(Subscription subscription, Object message) { if (subscription == null || subscription.subscriber.get() == null) { return; } try { subscription.subscriberMethod.method.invoke(subscription.subscriber.get(), message); } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } } }
MainEventHandler(在主執行緒執行):
public class MainEventHandler implements IEventHandler { private Handler mMainHandler = new Handler(Looper.getMainLooper()); DefaultEventHandler hanlder = new DefaultEventHandler(); @Override public void handleEvent(final Subscription subscription, final Object message) { mMainHandler.post(new Runnable() { @Override public void run() { hanlder.handleEvent(subscription, message); } }); } }
AsyncEventHandler(新開一個執行緒執行):
public class AsyncEventHandler implements IEventHandler { private DispatcherThread mDispatcherThread; private IEventHandler mEventHandler = new DefaultEventHandler(); public AsyncEventHandler() { mDispatcherThread = new DispatcherThread(AsyncEventHandler.class.getSimpleName()); mDispatcherThread.start(); } @Override public void handleEvent(final Subscription subscription, final Object message) { mDispatcherThread.post(new Runnable() { @Override public void run() { mEventHandler.handleEvent(subscription, message); } }); } private class DispatcherThread extends HandlerThread { // 關聯了AsyncExecutor訊息佇列的Handle Handler mAsyncHandler; DispatcherThread(String name) { super(name); } public void post(Runnable runnable) { if (mAsyncHandler == null) { throw new NullPointerException("mAsyncHandler == null, please call start() first."); } mAsyncHandler.post(runnable); } @Override public synchronized void start() { super.start(); mAsyncHandler = new Handler(this.getLooper()); } } }
以上便是釋出訊息的程式碼。
登出監聽
最後一個物件被銷燬還要登出監聽,否則容易導致記憶體洩露,目前SimpleEventBus用的是WeakReference,能夠透過GC自動回收,但不知道greenrobot/EventBus為什麼沒這樣實現,待研究。登出監聽其實就是遍歷Map,拿掉該物件的訂閱即可:
public class EventBus { ... public void unregister(Object subscriber) { if (subscriber == null) { return; } Iterator<CopyOnWriteArrayList<Subscription>> iterator = mSubscriptionsByEventtype.values().iterator(); while (iterator.hasNext()) { CopyOnWriteArrayList<Subscription> subscriptions = iterator.next(); if (subscriptions != null) { List<Subscription> foundSubscriptions = new LinkedList<>(); for (Subscription subscription : subscriptions) { Object cacheObject = subscription.subscriber.get(); if (cacheObject == null || cacheObject.equals(subscriber)) { foundSubscriptions.add(subscription); } } subscriptions.removeAll(foundSubscriptions); } // 如果針對某個Event的訂閱者數量為空了,那麼需要從map中清除 if (subscriptions == null || subscriptions.size() == 0) { iterator.remove(); } } } ... }
侷限性
由於時間關係,目前只研究了EventBus的核心部分,還有幾個值得深入研究的點,在此記錄一下,也歡迎路過的大牛指點一二。
效能問題
實際使用時,註解和反射會導致效能問題,但EventBus3已經透過Subscriber Index基本解決了這一問題,實現也非常有意思,是透過註解處理器(Annotation Processor)把耗時的邏輯從執行期提前到了編譯期,透過編譯期生成的索引來給執行期提速,這也是這個名字的由來
可用性問題
如果訂閱者很多會不會影響體驗,畢竟原始的方法是點對點的訊息傳遞,不會有這種問題,如果部分訂閱者尚未初始化怎麼辦。等等。目前EventBus3提供了優先順序和粘性事件的屬性來進一步滿足開發需求。但是否徹底解決問題了還有待驗證。
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69952849/viewspace-2664095/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 框架思維框架
- 大牛告訴你,只有突破程式設計師思維,才不會淪為碼農!程式設計師
- 大神告訴你如何理解微服務框架微服務框架
- 怎麼自學精益生產?過來人告訴你
- 創新思維框架:第一原則思維 - Neil Kakkar框架
- 我試圖透過這篇文章告訴你,什麼是神奇的泛化呼叫。
- java (基礎、框架)思維腦圖Java框架
- 幾何約束求解思維框架框架
- 告訴你什麼是TestOps測試運維運維
- 如何透過思維導圖讓你的專案管理高效提升50%?專案管理
- 化妝品怎麼選?新匯通告訴你告訴你包裝很重要!
- DBA的一天是怎樣的?運維工程師告訴你答案運維工程師
- 程式猿,讓我來告訴你怎麼追女生!!!
- 碼教授告訴你大資料該怎麼用大資料
- 自己動手實現一個EventBus框架框架
- Android開源框架原始碼鑑賞:EventBusAndroid框架原始碼
- 透過部署流行 Web 框架掌握 Serverless 技術Web框架Server
- 都在使用的快取,騰訊大牛告訴你他們是如何使用快取的快取
- 如何透過“就像XX”啟發創新思維? - mathiasverraes
- 我試圖透過這篇文章告訴你,這行原始碼有多牛逼。原始碼
- 用半勵志的方式告訴你,怎麼學習Python開發Python
- 我來告訴你,ChatGPT 該怎麼對接到自己的專案中!ChatGPT
- 精英都是方法控,做人做事必備的100套思維框架工具框架
- 國人的慣性思維,你中招了麼?
- 告訴你什麼是Pixelmator Pro for Mac!Mac
- 神經網路是怎麼“看”東西的?啟用圖告訴你神經網路
- 資料告訴你,抖音是怎麼在半年之內逆襲的
- 銀行科技到底怎麼樣?我曾經的四年告訴你 !
- Slack如何透過產品思維打造內部Devops平臺?dev
- 圖片侵權難題,華為雲告訴你怎麼辦!
- 怎麼使用路由框架Fluro?Flutter的這個框架太優秀了!路由框架Flutter
- 讓機器學習告訴你,你的siri在想什麼!機器學習
- 對laravel框架你瞭解多少,為什麼說是優雅的框架Laravel框架
- 面試完總是讓你回去等通知?不,告訴你怎麼搶救下!面試
- 怎麼凸顯主題?極思維
- 一張圖告訴你什麼是GraphQL?
- 一篇告訴你什麼是SpringSpring
- 【轉】kafka-告訴你什麼是kafkaKafka