国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 學院 > 開發設計 > 正文

老司機教你 “飆” EventBus 3

2019-11-09 18:11:27
字體:
來源:轉載
供稿:網友

Is what

基于觀察者模式的事件發布/訂閱框架。通過極少的代碼實現模塊間的通信,無須層層傳遞。使用方便,性能高,接入成本低,降低耦合,支持多線程的優點。

流程圖

3.0 新特性

在EventBus 3.0版本中引入了 EventBusAnnotationPRocessor(注解分析生成索引)技術,大大提高了EventBus的運行效率。

1. 使用

流程圖
1.1 添加依賴

App 的 build.gradle 中:

compile'org.greenrobot:eventbus:3.0.0'
1.2 添加加速索引

首先,在項目gradle的dependencies中引入apt編譯插件:

classpath'com.neenbedankt.gradle.plugins:android-apt:1.8'

然后,在App的build.gradle中應用apt插件,并設置apt生成的索引的包名和類名:

applyplugin:'com.neenbedankt.android-apt'apt { arguments{ eventBusIndex"com.study.sangerzhong.studyapp.MyEventBusIndex" }}

接著,在App的dependencies中引入EventBusAnnotationProcessor:

apt'org.greenrobot:eventbus-annotation-processor:3.0.1'

注意:

注解解析依賴于android-apt-plugin。加速索引可以不加應用EventBusAnnotationProcessor卻沒有設置arguments,編譯時會報錯:No option eventBusIndex passed to annotation processor此時需要先編譯一次,生成索引類。編譯成功之后,會在/ProjectName/app/build/generated/source/apt/PakageName/下看到通過注解分析生成的索引類,這樣便可以在初始化EventBus時應用生成的索引了。
1.3 初始化

兩種初始方式:

默認單例獲取(EventBus默認有一個單例)EventBus mEventBus = EventBus.getDefault();自定義//如:應用生成好的索引時EventBus mEventBus = EventBus.builder() .addIndex(new MyEventBusIndex()) .build();//如:自定義的設置應用到默認單利中EventBus mEventBus = EventBus.builder() .addIndex(newMyEventBusIndex()) .installDefaultEventBus();
1.4 定義事件

所有能被實例化為 Object 的實例都可以作為事件:

public class DriverEvent { public String info; }

注意:如果在EventBus 3.0中使用了索引加速,事件類的修飾符必須為public,不然編譯時會報錯:Subscriber method must be public。

1.5 監聽事件

在訂閱事件(接收事件)的模塊,注冊EventBus:

//如:Activity 中可寫在 onCreate() 方法內mEventBus.register(Object);

在訂閱事件(接收事件)的模塊,注銷EventBus:

//如:Activity 中可寫在 onDestory() 方法內mEventBus.unregister(Object);

3.0之前,需要區分是否監聽黏性(sticky)事件。3.0中,改為添加注解的形式:

@Subscribe(threadMode = ThreadMode.POSTING, priority =0, sticky =true)public void handleEvent ( DriverEventevent ){ Log.d(TAG,event.info);}

注解有三個參數:threadMode: 回調所在的線程priority: 優先級sticky: 是否接收黏性事件注冊了監聽的模塊必須有一個標注Subscribe注解方法,不然在register時會拋出異常:Subscriberclass XXX and its super classes havenopublic methods with the@Subscribeannotation

1.6 發送事件

調用post或postSticky即可:無須注冊

EventBus.getDefault().post(new DriverEvent("magnet:?xt=urn:btih……"));

以上便完成了使用EventBus的學習。實際使用中,register 和 unregister 通常與 Activity 和 Fragment 的生命周期相關,ThreadMode.MainThread 解決了界面刷新必須在UI線程的問題,黏性事件可以解決 post 與 register 同時執行時的異步問題,事件傳遞沒有序列化與反序列化的性能消耗。

2. 原理分析

2.1 和新架構
機制

訂閱者模塊需要通過EventBus訂閱相關的事件,并準備好處理事件的回調方法,而事件發布者則在適當的時機把事件post出去,EventBus就能搞定一切。在架構方面,EventBus 3.0與之前稍老版本有不同,如圖:

架構

核心類EventBus,其中subscriptionByEventType是以事件的類為key,訂閱者的回調方法為value的映射關系表。也就是說EventBus在收到一個事件時,可以根據這個事件的類型,在subscriptionByEventType中找到所有監聽了該事件的訂閱者及處理事件的回調方法。而typesBySubscriber則是每個訂閱者所監聽的事件類型表,在取消注冊時可以通過該表中保存的信息,快速刪除subscriptionByEventType中訂閱者的注冊信息,避免遍歷查找。注冊事件、發送事件和注銷都是圍繞著這兩個核心數據結構來展開。上面的Subscription可以理解為每個訂閱者與回調方法的關系,在其他模塊發送事件時,就會通過這個關系,讓訂閱者執行回調方法。

回調方法在這里被封裝成了SubscriptionMethod,里面保存了在需要反射invoke方法時的各種參數,包括優先級,是否接收黏性事件和所在線程等信息。而要生成這些封裝好的方法,則需要SubscriberMethodFinder,它可以在regster時得到訂閱者的所有回調方法,并封裝返回給EventBus。而右邊的加速器模塊,就是為了提高SubscriberMethodFinder的效率。

四個Poster,是EventBus能在不同的線程執行回調方法的核心:POSTING:(調用post所在的線程執行回調)不需要poster來調度,直接運行。MAIN:(UI線程回調)如果post所在線程為UI線程則直接執行,否則通過mainThreadPoster來調度。BACKGROUND:(Backgroud線程回調):如果post所在線程為非UI線程則直接執行,否則通過backgroundPoster來調度。ASYNC:(交給線程池來管理):直接通過asyncPoster調度。不同的Poster會在post事件時,調度相應的事件隊列PendingPostQueue,讓每個訂閱者的回調方法收到相應的事件,并在其注冊的Thread中運行。而這個事件隊列是一個鏈表,由一個個PendingPost組成,其中包含了事件,事件訂閱者,回調方法這三個核心參數,以及需要執行的下一個PendingPost。

2.2 register

根據訂閱者的類來找回調方法,把訂閱者和回調方法封裝成關系,并保存到相應的數據結構中,為隨后的事件分發做好準備,最后處理黏性事件:

register

EventBus 3.0使用了注解表示回調,可以出現相同的ThreadMode的回調方法監聽相同的事件,此時會根據注冊的先后順序,先注冊先分發事件,注意不是先收到事件,收到事件的順序還是得看poster中Handler的調度。

2.3 post

分析事件后,得到所有監聽該事件的訂閱者的回調方法,并利用反射來invoke方法,實現回調:

post

圖中看到poster的調度事件功能,同時調度的單位細化成了Subscription,即每一個方法都有自己的優先級和是否接收黏性事件。在源代碼中為了保證post執行不會出現死鎖,等待和對同一訂閱者發送相同的事件,增加了很多線程保護鎖和標志位,值得每個開發者學習。

2.4 unregister

把在注冊時往兩個數據結構中添加的訂閱者信息刪除即可:

unregister
2.5 黏性事件

為什么要有這個設計呢?舉個栗子:在登陸成功后自動播放歌曲,而登陸和監聽登陸是同時進行的。如果登陸流程走得特別快,在登陸成功后播放模塊才注冊了監聽。此時播放模塊便會錯過了【登陸成功】的事件,出現“雖然登陸成功了,回調卻沒執行”的情況。而如果【登陸成功】這個事件是一個黏性事件的話,那么即使我后來才注冊了監聽(并且回調方法設置為監聽黏性事件),則回調就能在注冊的那一刻被執行,這個問題就能被優雅地解決,而不需要額外去定義其他標志位。

3 索引加速

在EventBus 3.0的介紹中,作者提到以前的版本為了保證性能,在遍歷尋找訂閱者的回調方法時使用反射而不是注解。但現在卻能在使用注解的前提下,大幅度提高性能,同時作者在博客中放出了這張對比圖:

速度對比

性能方面,EventBus 3.0由于使用了注解,比起使用反射來遍歷方法的2.4版本遜色不少。但開啟索引后性能遠遠超出之前的版本。

關于索引加速的具體分析請看原文。

4 其他

4.1 混淆

EventBus 3.0使用注解的方式。作者的意思是在混淆時就不用再keep住相應的類和方法。

//運行時,拋出錯誤java.lang.NoSuchFieldError: No static field POSTING。//解決方法:keep住所有eventbus相關的代碼-keepclassde.greenrobot.** {*;}

分析,因為在SubscriberMethodFinder的findUsingReflection方法中,在調用Method.getAnnotation()時獲取ThreadMode這個enum失敗了,所以只需要keep住這個enum就可以了(如下)。

-keeppublicenumorg.greenrobot.eventbus.ThreadMode { publicstatic*; }

這樣就能編譯通過了,如果使用了索引加速,是不會有上面這個問題的。因為在找方法時,調用的不是findUsingReflection,而是findUsingInfo。

//使用索引加速后,編譯后拋出錯誤:Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

因為生成索引GeneratedSubscriberIndex是在代碼混淆之前進行的,混淆之后類名和方法名都不一樣了(上面這個錯誤是方法無法找到),得keep住所有被Subscribe注解標注的方法:

-keepclassmembersclass* { @de.greenrobot.event.Subscribe ;}

這里就得權衡一下利弊:使用了注解不用索引加速,則只需要keep住EventBus相關的代碼,現有的代碼可以正常的進行混淆。而使用了索引加速的話,則需要keep住相關的方法和類。

4.2 跨進程

目前EventBus只支持跨線程,而不支持跨進程。如果一個app的service起到了另一個進程中,那么注冊監聽的模塊則會收不到另一個進程的EventBus發出的事件。這里可以考慮利用IPC做映射表,并在兩個進程中各維護一個EventBus,不過這樣就要自己去維護register和unregister的關系,比較繁瑣,而且這種情況下通常用廣播會更加方便,大家可以思考一下有沒有更優的解決方案。

4.3 事件環路

在使用EventBus時,通常會把兩個模塊相互監聽,來達到一個相互回調通信的目的。但這樣一旦出現死循環,而且如果沒有相應的日志信息,很難定位問題。所以在使用EventBus的模塊,如果在回調上有環路,而且回調方法復雜到了一定程度的話,就要考慮把接收事件專門封裝成一個子模塊,同時考慮避免出現事件環路。

5 寫在最后

EventBus并不是重構代碼的唯一之選。作為觀察者模式的“同門師兄弟”——RxJava,作為功能更為強大的響應式編程框架,可以輕松實現EventBus的事件總線功能(RxBus)。但畢竟大型項目要接入RxJava的成本高,復雜的操作符需要開發者投入更多的時間去學習。所以想在成熟的項目中快速地重構、解耦模塊,EventBus依舊是不二之選。

老司機發車了
發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 开封市| 大新县| 大丰市| 仙桃市| 资中县| 于田县| 洞口县| 会东县| 汾阳市| 阿克苏市| 通辽市| 宁波市| 长丰县| 莎车县| 两当县| 西乡县| 磐石市| 山西省| 吉隆县| 天台县| 夹江县| 临沧市| 万全县| 容城县| 农安县| 东丰县| 长治市| 保德县| 和平区| 湘西| 花莲市| 泰来县| 汽车| 威远县| 克什克腾旗| 潮安县| 大新县| 汉寿县| 诸城市| 铅山县| 新乡市|