<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      【Bugly干貨分享】老司機(jī)教你 “飆” EventBus 3

      Bugly 技術(shù)干貨系列內(nèi)容主要涉及移動(dòng)開發(fā)方向,是由 Bugly 邀請騰訊內(nèi)部各位技術(shù)大咖,通過日常工作經(jīng)驗(yàn)的總結(jié)以及感悟撰寫而成,內(nèi)容均屬原創(chuàng),轉(zhuǎn)載請標(biāo)明出處。

      EventBus對于Android開發(fā)老司機(jī)來說肯定不會陌生,它是一個(gè)基于觀察者模式的事件發(fā)布/訂閱框架,開發(fā)者可以通過極少的代碼去實(shí)現(xiàn)多個(gè)模塊之間的通信,而不需要以層層傳遞接口的形式去單獨(dú)構(gòu)建通信橋梁。從而降低因多重回調(diào)導(dǎo)致的模塊間強(qiáng)耦合,同時(shí)避免產(chǎn)生大量內(nèi)部類。它擁有使用方便,性能高,接入成本低和支持多線程的優(yōu)點(diǎn),實(shí)乃模塊解耦、代碼重構(gòu)必備良藥。

      作為Markus Junginger大神耗時(shí)4年打磨、超過1億接入量、Github 9000+ star的明星級組件,分析EventBus的文章早已是數(shù)不勝數(shù)。本文的題目是“教你飆巴士”,而這輛Bus之所以可以飆起來,是因?yàn)樽髡咴贓ventBus 3中引入了EventBusAnnotationProcessor(注解分析生成索引)技術(shù),大大提高了EventBus的運(yùn)行效率。而分析這個(gè)加速器的資料在網(wǎng)上很少,因此本文會把重點(diǎn)放在分析這個(gè)EventBus 3的新特性上,同時(shí)分享一些踩坑經(jīng)驗(yàn),并結(jié)合源碼分析及UML圖,以直觀的形式和大家一起學(xué)習(xí)EventBus 3的用法及運(yùn)行原理。

      1. 新手上路——使用EventBus

      1.1 導(dǎo)入組件:

      打開App的build.gradle,在dependencies中添加最新的EventBus依賴:

      compile 'org.greenrobot:eventbus:3.0.0'
      

      如果不需要索引加速的話,就可以直接跳到第二步了。而要應(yīng)用最新的EventBusAnnotationProcessor則比較麻煩,因?yàn)樽⒔饨馕鲆蕾囉赼ndroid-apt-plugin。我們一步一步來,首先在項(xiàng)目gradle的dependencies中引入apt編譯插件:

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

      然后在App的build.gradle中應(yīng)用apt插件,并設(shè)置apt生成的索引的包名和類名:

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

      接著在App的dependencies中引入EventBusAnnotationProcessor:

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

      這里需要注意,如果應(yīng)用了EventBusAnnotationProcessor卻沒有設(shè)置arguments的話,編譯時(shí)就會報(bào)錯(cuò):
      No option eventBusIndex passed to annotation processor

      此時(shí)需要我們先編譯一次,生成索引類。編譯成功之后,就會發(fā)現(xiàn)在\ProjectName\app\build\generated\source\apt\PakageName\下看到通過注解分析生成的索引類,這樣我們便可以在初始化EventBus時(shí)應(yīng)用我們生成的索引了。

      1.2 初始化EventBus

      EventBus默認(rèn)有一個(gè)單例,可以通過getDefault()獲取,也可以通過EventBus.builder()構(gòu)造自定義的EventBus,比如要應(yīng)用我們生成好的索引時(shí):

      EventBus mEventBus = EventBus.builder().addIndex(new MyEventBusIndex()).build();
      

      如果想把自定義的設(shè)置應(yīng)用到EventBus默認(rèn)的單例中,則可以用installDefaultEventBus()方法:

      EventBus.builder().addIndex(new MyEventBusIndex()).installDefaultEventBus();
      

      1.3 定義事件:

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

      public class DriverEvent { public String info; }
      

      在最新版的eventbus 3中如果用到了索引加速,事件類的修飾符必須為public,不然編譯時(shí)會報(bào)錯(cuò):Subscriber method must be public

      1.4 監(jiān)聽事件:

      首先把作為訂閱事件的模塊通過EventBus注冊監(jiān)聽:

      mEventBus.register(this);
      

      在3.0之前,注冊監(jiān)聽需要區(qū)分是否監(jiān)聽黏性(sticky)事件,監(jiān)聽EventBus事件的模塊需要實(shí)現(xiàn)以onEvent開頭的方法。如今改為在方法上添加注解的形式:

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

      注解有三個(gè)參數(shù),threadMode為回調(diào)所在的線程,priority為優(yōu)先級,sticky為是否接收黏性事件。調(diào)度單位從類細(xì)化到了方法,對方法的命名也沒有了要求,方便混淆代碼。但注冊了監(jiān)聽的模塊必須有一個(gè)標(biāo)注了Subscribe注解方法,不然在register時(shí)會拋出異常:
      Subscriber class XXX and its super classes have no public methods with the @Subscribe annotation

      1.5 發(fā)送事件:

      調(diào)用post或者postSticky即可:

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

      到此我們就完成了使用EventBus的學(xué)習(xí),可以在代碼中盡情地飚車了。項(xiàng)目接入了EventBus之后會有什么好處呢?舉一個(gè)常見的用例:ViewPager中Fragment的相互通信,就不需要在容器中定義各種接口,可以直接通過EventBus來實(shí)現(xiàn)相互回調(diào),這樣就把邏輯從ViewPager這個(gè)容器中剝離出來,使代碼閱讀起來更加直觀。

      在實(shí)際項(xiàng)目的使用中,register和unregister通常與Activity和Fragment的生命周期相關(guān),ThreadMode.MainThread可以很好地解決Android的界面刷新必須在UI線程的問題,不需要再回調(diào)后用Handler中轉(zhuǎn)(EventBus中已經(jīng)自動(dòng)用Handler做了處理),黏性事件可以很好地解決post與register同時(shí)執(zhí)行時(shí)的異步問題(這個(gè)在原理中會說到),事件的傳遞也沒有序列化與反序列化的性能消耗,足以滿足我們大部分情況下的模塊間通信需求。

      2. 變身老司機(jī)——EventBus原理分析

      在平時(shí)使用中我們不需要關(guān)心EventBus中對事件的分發(fā)機(jī)制,但要成為能夠快速排查問題的老司機(jī),我們還是得熟悉它的工作原理,下面我們就透過UML圖來學(xué)習(xí)一下。

      2.1 核心架構(gòu)

      EventBus的核心工作機(jī)制透過作者Blog中的這張圖就能很好地理解:

      訂閱者模塊需要通過EventBus訂閱相關(guān)的事件,并準(zhǔn)備好處理事件的回調(diào)方法,而事件發(fā)布者則在適當(dāng)?shù)臅r(shí)機(jī)把事件post出去,EventBus就能幫我們搞定一切。在架構(gòu)方面,EventBus 3與之前稍老版本有不同,我們直接看架構(gòu)圖:

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

      回調(diào)方法在這里被封裝成了SubscriptionMethod,里面保存了在需要反射invoke方法時(shí)的各種參數(shù),包括優(yōu)先級,是否接收黏性事件和所在線程等信息。而要生成這些封裝好的方法,則需要SubscriberMethodFinder,它可以在regster時(shí)得到訂閱者的所有回調(diào)方法,并封裝返回給EventBus。而右邊的加速器模塊,就是為了提高SubscriberMethodFinder的效率,會在第三章詳細(xì)介紹,這里就不再啰嗦。

      而下面的三個(gè)Poster,則是EventBus能在不同的線程執(zhí)行回調(diào)方法的核心,我們根據(jù)不同的回調(diào)方式來看:

      1. POSTING(在調(diào)用post所在的線程執(zhí)行回調(diào)):不需要poster來調(diào)度,直接運(yùn)行。
      2. MAIN(在UI線程回調(diào)):如果post所在線程為UI線程則直接執(zhí)行,否則則通過mainThreadPoster來調(diào)度。
      3. BACKGROUND(在Backgroud線程回調(diào)):如果post所在線程為非UI線程則直接執(zhí)行,否則則通過backgroundPoster來調(diào)度。
      4. ASYNC(交給線程池來管理):直接通過asyncPoster調(diào)度。

      可以看到,不同的Poster會在post事件時(shí),調(diào)度相應(yīng)的事件隊(duì)列PendingPostQueue,讓每個(gè)訂閱者的回調(diào)方法收到相應(yīng)的事件,并在其注冊的Thread中運(yùn)行。而這個(gè)事件隊(duì)列是一個(gè)鏈表,由一個(gè)個(gè)PendingPost組成,其中包含了事件,事件訂閱者,回調(diào)方法這三個(gè)核心參數(shù),以及需要執(zhí)行的下一個(gè)PendingPost。

      至此EventBus 3的架構(gòu)就分析完了,與之前EventBus老版本最明顯的區(qū)別在于:分發(fā)事件的調(diào)度單位從訂閱者,細(xì)化成了訂閱者的回調(diào)方法。也就是說每個(gè)回調(diào)方法都有自己的優(yōu)先級,執(zhí)行線程和是否接收黏性事件,提高了事件分發(fā)的靈活程度,接下來我們在看核心功能的實(shí)現(xiàn)時(shí)更能體現(xiàn)這一點(diǎn)。

      2.2 register

      簡單來說就是:根據(jù)訂閱者的類來找回調(diào)方法,把訂閱者和回調(diào)方法封裝成關(guān)系,并保存到相應(yīng)的數(shù)據(jù)結(jié)構(gòu)中,為隨后的事件分發(fā)做好準(zhǔn)備,最后處理黏性事件:

      值得注意的是,老版本的EventBus是允許事件訂閱者以不同的ThreadMode去監(jiān)聽同一個(gè)事件的,即在一個(gè)訂閱者中有多個(gè)方法訂閱一個(gè)事件,此時(shí)是無法保證這幾個(gè)回調(diào)的先后順序的,因?yàn)椴煌木€程回調(diào)是通過Handler調(diào)度的,有可能單個(gè)線程中的事件過多,事件受阻,回調(diào)則會比較慢。如今EventBus 3使用了注解來表示回調(diào)后,還可以出現(xiàn)相同的ThreadMode的回調(diào)方法監(jiān)聽相同的事件,此時(shí)會根據(jù)注冊的先后順序,先注冊先分發(fā)事件,注意不是先收到事件,收到事件的順序還是得看poster中Handler的調(diào)度。

      2.3 post

      總的來說就是分析事件,得到所有監(jiān)聽該事件的訂閱者的回調(diào)方法,并利用反射來invoke方法,實(shí)現(xiàn)回調(diào):

      這里就能看到poster的調(diào)度事件功能,同時(shí)可以看到調(diào)度的單位細(xì)化成了Subscription,即每一個(gè)方法都有自己的優(yōu)先級和是否接收黏性事件。在源代碼中為了保證post執(zhí)行不會出現(xiàn)死鎖,等待和對同一訂閱者發(fā)送相同的事件,增加了很多線程保護(hù)鎖和標(biāo)志位,值得我們每個(gè)開發(fā)者學(xué)習(xí)。

      2.4 unregister

      注銷就比較簡單了,把在注冊時(shí)往兩個(gè)數(shù)據(jù)結(jié)構(gòu)中添加的訂閱者信息刪除即可:

      上面經(jīng)常會提到黏性事件,為什么要有這個(gè)設(shè)計(jì)呢?這里舉個(gè)例子:我想在登陸成功后自動(dòng)播放歌曲,而登陸和監(jiān)聽登陸監(jiān)聽是同時(shí)進(jìn)行的。在這個(gè)前提下,如果登陸流程走得特別快,在登陸成功后播放模塊才注冊了監(jiān)聽。此時(shí)播放模塊便會錯(cuò)過了【登陸成功】的事件,出現(xiàn)“雖然登陸成功了,回調(diào)卻沒執(zhí)行”的情況。而如果【登陸成功】這個(gè)事件是一個(gè)黏性事件的話,那么即使我后來才注冊了監(jiān)聽(并且回調(diào)方法設(shè)置為監(jiān)聽黏性事件),則回調(diào)就能在注冊的那一刻被執(zhí)行,這個(gè)問題就能被優(yōu)雅地解決,而不需要額外去定義其他標(biāo)志位。

      至此大家對EventBus的運(yùn)行原理應(yīng)該有了一定的了解,雖然看起來像是一個(gè)復(fù)雜耗時(shí)的自動(dòng)機(jī),但大部分時(shí)候事件都是一瞬間就能分發(fā)到位的,而大家關(guān)心的性能問題反而是發(fā)生在注冊EventBus的時(shí)候,因?yàn)樾枰闅v監(jiān)聽者的所有方法去找到回調(diào)的方法。作者也提到運(yùn)行時(shí)注解的性能在Android上并不理想,為了解決這個(gè)問題,作者才會以索引的方式去生成回調(diào)方法表(下一章會詳細(xì)介紹)。而EventBus源碼分析的文章早已是數(shù)不勝數(shù),這里就不再大段大段地貼代碼了,主要以類圖和流程圖的形式讓大家直觀地了解EventBus3的整體架構(gòu)及核心功能的實(shí)現(xiàn)原理,把源碼分析留到后面介紹EventBusAnnotationProcessor中再進(jìn)行。大家如果想要深入學(xué)習(xí)EventBus 3的話,在本文結(jié)尾的參考文章中有很多寫得很棒的源碼分析。

      3. 渦輪引擎——索引加速

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

      可以看到在性能方面,EventBus 3由于使用了注解,比起使用反射來遍歷方法的2.4版本遜色不少。但開啟了索引后性能像打了雞血一樣,遠(yuǎn)遠(yuǎn)超出之前的版本。這里我們就來分析一下這個(gè)提高EventBus性能的“渦輪引擎”。(下面的源碼分析為了方便閱讀,添加了部分注釋,并刪減了部分源碼,如果有疑問的話可以到官方的github上查看原版源碼)

      首先我們知道,索引是在初始化EventBus時(shí)通過EventBusBuilder.addIndex(SubscriberInfoIndex index)方法傳進(jìn)來的,我們就先看看這個(gè)方法:

      public EventBusBuilder addIndex(SubscriberInfoIndex index) {
          if(subscriberInfoIndexes == null) {
              subscriberInfoIndexes = new ArrayList<>();
          }
          subscriberInfoIndexes.add(index);
          return this;
      }
      

      可以看到,傳進(jìn)來的索引信息會保存在subscriberInfoIndexes這個(gè)List中,后續(xù)會通過EventBusBuilder傳到相應(yīng)EventBus的SubscriberMethodFinder實(shí)例中。我們先來分析SubscriberInfoIndex這個(gè)參數(shù):

      public interface SubscriberInfoIndex {
          SubscriberInfo getSubscriberInfo(Class<?> subscriberClass);
      }
      

      可見索引只需要做一件事情——就是能拿到訂閱者的信息。而實(shí)現(xiàn)這個(gè)接口的類如果我們沒有編譯過,是找不到的。這里就得看我們在一開始在配置gradle時(shí)導(dǎo)入的EventBusAnnotationProcessor:

      @SupportedAnnotationTypes("org.greenrobot.eventbus.Subscribe")
      @SupportedOptions(value = {"eventBusIndex", "verbose"})
      public class EventBusAnnotationProcessor extends AbstractProcessor {
          /** Found subscriber methods for a class (without superclasses). 被注解表示的方法信息 */ 
          private final ListMap<TypeElement, ExecutableElement> methodsByClass = new ListMap<>();
          private final Set<TypeElement> classesToSkip = new HashSet<>(); // checkHasErrors檢查出來的異常方法
      
          @Override
          public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment env) {
              Messager messager = processingEnv.getMessager();
              try {
                  String index = processingEnv.getOptions().get(OPTION_EVENT_BUS_INDEX);
                  if (index == null) { // 如果沒有在gradle中配置apt的argument,編譯就會在這里報(bào)錯(cuò)
                      messager.printMessage(Diagnostic.Kind.ERROR, "No option " + OPTION_EVENT_BUS_INDEX +
                              " passed to annotation processor");
                      return false;
                  }
                  /** ... */
                  collectSubscribers(annotations, env, messager); // 根據(jù)注解拿到所有訂閱者的回調(diào)方法信息
                  checkForSubscribersToSkip(messager, indexPackage); // 篩掉不符合規(guī)則的訂閱者
                  if (!methodsByClass.isEmpty()) {
                      createInfoIndexFile(index); // 生成索引類
                  } 
                  /** 打印錯(cuò)誤 */
          }
      
          /** 下面這些方法就不再貼出具體實(shí)現(xiàn)了,我們了解它們的功能就行 */
          private void collectSubscribers // 遍歷annotations,找出所有被注解標(biāo)識的方法,以初始化methodsByClass
          private boolean checkHasNoErrors // 過濾掉static,非public和參數(shù)大于1的方法
          private void checkForSubscribersToSkip // 檢查methodsByClass中的各個(gè)類,是否存在非public的父類和方法參數(shù)
          /** 下面這三個(gè)方法會把methodsByClass中的信息寫到相應(yīng)的類中 */
          private void writeCreateSubscriberMethods
          private void createInfoIndexFile
          private void writeIndexLines
      }
      

      至此便揭開了索引生成的秘密,是在編譯時(shí)apt插件通過EventBusAnnotationProcessor分析注解,并利用注解標(biāo)識的相關(guān)類的信息去生成相關(guān)的類。writeCreateSubscriberMethods中調(diào)用了很多IO函數(shù),很容易理解,這里就不貼了,我們直接看生成出來的類:

      /** This class is generated by EventBus, do not edit. */
      public class MyEventBusIndex implements SubscriberInfoIndex {
      private static final Map<Class<?>, SubscriberInfo> SUBSCRIBER_INDEX;
          static {
              SUBSCRIBER_INDEX = new HashMap<Class<?>, SubscriberInfo>();
              // 每有一個(gè)訂閱者類,就調(diào)用一次putIndex往索引中添加相關(guān)的信息
              putIndex(new SimpleSubscriberInfo(com.study.sangerzhong.studyapp.ui.MainActivity.class, true, new SubscriberMethodInfo[] {
                  new SubscriberMethodInfo("onEvent", com.study.sangerzhong.studyapp.ui.MainActivity.DriverEvent.class, ThreadMode.POSTING, 0, false), 
                  // 類中每一個(gè)被Subscribe標(biāo)識的方法都在這里添加進(jìn)來
              })); 
          }
          // 下面的代碼就是EventBusAnnotationProcessor中寫死的了
          private static void putIndex(SubscriberInfo info) {
              SUBSCRIBER_INDEX.put(info.getSubscriberClass(), info);
          }
      
          @Override
          public SubscriberInfo getSubscriberInfo(Class<?> subscriberClass) {
              SubscriberInfo info = SUBSCRIBER_INDEX.get(subscriberClass);
              if (info != null) {
                  return info;
              } else {
                  return null;
              }
          }
      }
      

      可見,子類中hardcode了所有注冊了EventBus的類中被Subscribe注解標(biāo)識的方法信息,包括方法名、方法參數(shù)類型等信息。并把這些信息封裝到SimpleSubscriberInfo中,我們拿到的索引其實(shí)就是以訂閱者的類為Key、SimpleSubscriberInfo為value的哈希表。而這些hardcode都是在編譯的時(shí)候生成的,避免了在在EventBus.register()時(shí)才去遍歷查找生成,從而把在注冊時(shí)需要遍歷訂閱者所有方法的行為,提前到在編譯時(shí)完成了。

      索引的生成我們已經(jīng)明白了,那么它是在哪里被應(yīng)用的呢?我們記得在注冊時(shí),EventBus會通過SubscriberMethodFinder來遍歷注冊對象的Class的所有方法,篩選出符合規(guī)則的方法,并作為訂閱者在接收到事件時(shí)執(zhí)行的回調(diào),我們直接來看看SubscriberMethodFinder.findSubscriberMethods()這個(gè)核心方法:

      List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
          List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass); 
          if (subscriberMethods != null) {
              return subscriberMethods; // 先去方法緩存里找,找到直接返回
          }
          if (ignoreGeneratedIndex) { // 是否忽略設(shè)置的索引
              subscriberMethods = findUsingReflection(subscriberClass);
          } else {
              subscriberMethods = findUsingInfo(subscriberClass);
          }
          /** 把找到的方法保存到METHOD_CACHE里并返回,找不到直接拋出異常 */
      }
      

      可以看到其中findUsingInfo()方法就是去索引中查找訂閱者的回調(diào)方法,我們戳進(jìn)去看看這個(gè)方法的實(shí)現(xiàn):

      private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
          // 最新版的EventBus3中,尋找方法時(shí)所需的臨時(shí)變量都被封裝到了FindState這個(gè)靜態(tài)內(nèi)部類中
          FindState findState = prepareFindState(); // 到對象池中取得上下文,避免頻繁創(chuàng)造對象,這個(gè)設(shè)計(jì)很贊
          findState.initForSubscriber(subscriberClass); // 初始化尋找方法的上下文
          while (findState.clazz != null) { // 子類找完了,會繼續(xù)去父類中找
              findState.subscriberInfo = getSubscriberInfo(findState); // 獲得訂閱者類的相關(guān)信息
              if (findState.subscriberInfo != null) { // 上一步能拿到相關(guān)信息的話,就開始把方法數(shù)組封裝成List
                  SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
                  for (SubscriberMethod subscriberMethod : array) {
                      if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                          // checkAdd是為了避免在父類中找到的方法是被子類重寫的,此時(shí)應(yīng)該保證回調(diào)時(shí)執(zhí)行子類的方法
                          findState.subscriberMethods.add(subscriberMethod);
                      }
                  }
              } else { // 索引中找不到,降級成運(yùn)行時(shí)通過注解和反射去找
                  findUsingReflectionInSingleClass(findState);
              }
              findState.moveToSuperclass(); // 上下文切換成父類
          }
          return getMethodsAndRelease(findState); // 找完后,釋放FindState進(jìn)對象池,并返回找到的回調(diào)方法
      }
      

      可以看到EventBus中在查找訂閱者的回調(diào)方法時(shí)是能處理好繼承關(guān)系的,不僅會去遍歷父類,而且還會避免因?yàn)橹貙懛椒▽?dǎo)致執(zhí)行多次回調(diào)。其中需要關(guān)心的是getSubscriberInfo()是如何返回索引數(shù)據(jù)的,我們繼續(xù)深入:

      private SubscriberInfo getSubscriberInfo(FindState findState) {
          if (findState.subscriberInfo != null && findState.subscriberInfo.getSuperSubscriberInfo() != null) { // subscriberInfo已有實(shí)例,證明本次查找需要查找上次找過的類的父類
              SubscriberInfo superclassInfo = findState.subscriberInfo.getSuperSubscriberInfo();
              if (findState.clazz == superclassInfo.getSubscriberClass()) { // 確定是所需查找的類
                  return superclassInfo;
              }
          }
          if (subscriberInfoIndexes != null) { // 從我們傳進(jìn)來的subscriberInfoIndexes中獲取相應(yīng)的訂閱者信息
              for (SubscriberInfoIndex index : subscriberInfoIndexes) {
                  SubscriberInfo info = index.getSubscriberInfo(findState.clazz);
                  if (info != null) { return info; }
              }
          }
          return null;
      }
      

      可見就在這個(gè)方法里面,應(yīng)用到了我們生成的索引,避免我們需要在findSubscriberMethods時(shí)去調(diào)用耗時(shí)的findUsingReflection方法。在實(shí)際使用中,Nexus6上一個(gè)Activity注冊EventBus需要10毫秒左右,而使用了索引后能降低到3毫秒左右,效果非常明顯。雖然這個(gè)索引的實(shí)現(xiàn)邏輯有點(diǎn)繞,而且還存在一些坑(比如后面講到的混淆問題),但實(shí)現(xiàn)的手段非常巧妙,尤其是“把耗時(shí)的操作在編譯的時(shí)候完成”和“用對象池減少創(chuàng)建對象的性能開銷”的思想值得我們開發(fā)者借鑒。

      4. 駕駛寶典——踩坑與經(jīng)驗(yàn)

      4.1 混淆問題

      混淆作為版本發(fā)布必備的流程,經(jīng)常會鬧出很多奇奇怪怪的問題,且不方便定位,尤其是EventBus這種依賴反射技術(shù)的庫。通常情況下都會把相關(guān)的類和回調(diào)方法都keep住,但這樣其實(shí)會留下被人反編譯后破解的后顧之憂,所以我們的目標(biāo)是keep最少的代碼。

      首先,因?yàn)镋ventBus 3棄用了反射的方式去尋找回調(diào)方法,改用注解的方式。作者的意思是在混淆時(shí)就不用再keep住相應(yīng)的類和方法。但是我們在運(yùn)行時(shí),卻會報(bào)java.lang.NoSuchFieldError: No static field POSTING。網(wǎng)上給出的解決辦法是keep住所有eventbus相關(guān)的代碼:

      -keep class de.greenrobot.** {*;}
      

      其實(shí)我們仔細(xì)分析,可以看到是因?yàn)樵赟ubscriberMethodFinder的findUsingReflection方法中,在調(diào)用Method.getAnnotation()時(shí)獲取ThreadMode這個(gè)enum失敗了,所以我們只需要keep住這個(gè)enum就可以了(如下)。

      -keep public enum org.greenrobot.eventbus.ThreadMode { public static *; }
      

      這樣就能正常編譯通過了,但如果使用了索引加速,是不會有上面這個(gè)問題的。因?yàn)樵谡曳椒〞r(shí),調(diào)用的不是findUsingReflection,而是findUsingInfo。但是使用了索引加速后,編譯后卻會報(bào)新的錯(cuò)誤:Could not find subscriber method in XXX Class. Maybe a missing ProGuard rule?

      這就很好理解了,因?yàn)樯伤饕鼼eneratedSubscriberIndex是在代碼混淆之前進(jìn)行的,混淆之后類名和方法名都不一樣了(上面這個(gè)錯(cuò)誤是方法無法找到),得keep住所有被Subscribe注解標(biāo)注的方法:

      -keepclassmembers class * {
          @de.greenrobot.event.Subscribe <methods>;
      }
      

      所以又倒退回了EventBus2.4時(shí)不能混淆onEvent開頭的方法一樣的處境了。所以這里就得權(quán)衡一下利弊:使用了注解不用索引加速,則只需要keep住EventBus相關(guān)的代碼,現(xiàn)有的代碼可以正常的進(jìn)行混淆。而使用了索引加速的話,則需要keep住相關(guān)的方法和類。

      4.2 跨進(jìn)程問題

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

      4.3 事件環(huán)路問題

      在使用EventBus時(shí),通常我們會把兩個(gè)模塊相互監(jiān)聽,來達(dá)到一個(gè)相互回調(diào)通信的目的。但這樣一旦出現(xiàn)死循環(huán),而且如果沒有相應(yīng)的日志信息,很難定位問題。所以在使用EventBus的模塊,如果在回調(diào)上有環(huán)路,而且回調(diào)方法復(fù)雜到了一定程度的話,就要考慮把接收事件專門封裝成一個(gè)子模塊,同時(shí)考慮避免出現(xiàn)事件環(huán)路。

      5. 車神之路——寫在最后

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

      本文總結(jié)了EventBus 3的使用方法,運(yùn)行原理和一些新特性,讓大家能直觀地看到這個(gè)組件的優(yōu)點(diǎn)和缺點(diǎn),同時(shí)讓大家在考慮是否在項(xiàng)目中引入EventBus時(shí)心里有個(gè)底。最后感謝Markus Junginger大神開源了如此實(shí)用的組件,以及組內(nèi)同事在筆者探究EventBus原理時(shí)提供的幫助,希望大家在看完本文后都能有所收獲,成為NB的Android開發(fā)老司機(jī)。

      參考:
      1. EventBus 3 Beta
      2. Markus Junginger - EventBus 3 beta announced at droidcon
      3. Skykai - EventBus 3.0 源代碼分析
      4. yydcdut - EventBus3.0源碼解析
      5. TmWork - EventBus源碼解析
      6. 鴻洋 - Android打造編譯時(shí)注解解析框架
      7. YoKey - 用RxJava實(shí)現(xiàn)事件總線(Event Bus)

      更多精彩內(nèi)容歡迎關(guān)注Bugly的微信公眾賬號:

      騰訊 Bugly 是一款專為移動(dòng)開發(fā)者打造的質(zhì)量監(jiān)控工具,幫助開發(fā)者快速,便捷的定位線上應(yīng)用崩潰的情況以及解決方案。智能合并功能幫助開發(fā)同學(xué)把每天上報(bào)的數(shù)千條 Crash 根據(jù)根因合并分類,每日日報(bào)會列出影響用戶數(shù)最多的崩潰,精準(zhǔn)定位功能幫助開發(fā)同學(xué)定位到出問題的代碼行,實(shí)時(shí)上報(bào)可以在發(fā)布后快速的了解應(yīng)用的質(zhì)量情況,適配最新的 iOS, Android 官方操作系統(tǒng),鵝廠的工程師都在使用,快來加入我們吧…

      ?
      posted @ 2016-05-09 18:48  騰訊bugly  閱讀(9712)  評論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 国内永久福利在线视频图片| 男女激情一区二区三区| 夜色福利站WWW国产在线视频| 婷婷99视频精品全部在线观看 | 久久九九精品国产免费看小说| 久久一区二区三区黄色片| 国产日产亚洲系列av| 精品人妻少妇一区二区三区在线| 狠狠躁夜夜躁人人爽天天5| 日韩人妖精品一区二区av| 日韩有码中文在线观看| 午夜精品一区二区三区成人| 四虎国产精品永久在线| 99福利一区二区视频| 91久久夜色精品国产网站| 少妇仑乱a毛片无码| 麻豆麻豆麻豆麻豆麻豆麻豆| 97色伦97色伦国产| 午夜免费啪视频| 老女老肥熟国产在线视频| 国产精品第一区亚洲精品| 日本一区二区三区专线| 成午夜福利人试看120秒| 在线观看国产成人av片| 国产精品无码免费播放| 亚洲国产综合精品2020| 免费观看羞羞视频网站| 欧美亚洲熟妇一区二区三区 | 久久精品国产成人午夜福利| 亚洲精品一区二区三区片| 黑人巨大videos极度另类| 艳妇乳肉豪妇荡乳av无码福利| 婷婷成人丁香五月综合激情| 草裙社区精品视频播放| 中文字幕无码不卡在线| 国产成人精品亚洲高清在线| 国色精品卡一卡2卡3卡4卡在线| 亚洲V天堂V手机在线| 四虎影视www在线播放| 亚洲欧美成人久久综合中文网| 亚洲偷自拍国综合|