【ソース解読】EventBus 3.0登録プロセス及びイベント配信ソース分析

24697 ワード

一、EventBusの概要及び使用例
公式サイトの定義を参照:
EventBus is a publish/subscribe event bus optimized for Android.
EventBusはAndroid向けに最適化されたパブリケーション/サブスクリプション・イベントバスであり、主な機能はコンポーネント間のメッセージングを簡素化することである.Activity、Fragment、Service、バックグラウンドスレッドでのHandler、Intent、BroadCastの代わりにEventBusを使用します.オーバーヘッドが小さく、コードが優雅で、送信者と受信者をデカップリングするなどの利点がある.そのgithubのウェブサイトはhttps://github.com/greenrobot/EventBus.
EventBusの使用
EventBusの使用は4つのステップにまとめることができ、以下はコードを結合することによって簡単に説明する.
1.イベントを定義します.ここのイベントは普通のJavaクラスです
class AEvent{
  //                   
}

2.コンポーネントを登録します.Activityを例にとると、onCreate()メソッドに登録され、onDestroy()に登録解除されます.Fragment、Serviceでもご利用いただけます
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    EventBus.getDefault().register(this);
  }
  public void onDestroy() {
    super.onDestroy();
    EventBus.getDefault().unregister(this);
  }

3.イベントを購読します.3.0より前にEventBusは4つのイベントの購読方法、onEvent、onEventMainThread、onEventBackground、onEventAsyncを提供した.その違いは簡単に言えば、応答イベントの実行スレッドを区別することである.3.0以降,@Subscribe注釈によりイベント応答法を自由に定義できる.以下は公式ドキュメントの栗です.
// This method will be called when a MessageEvent is posted (in the UI thread for Toast)
@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent(MessageEvent event) {
    Toast.makeText(getActivity(), event.message, Toast.LENGTH_SHORT).show();
}

// This method will be called when a SomeOtherEvent is posted
@Subscribe
public void handleSomethingElse(SomeOtherEvent event) {
    doSomethingWith(event);
}

上記のコードではthreadMode=ThreadMode.MAINによってプライマリ・スレッドで応答イベントを指定し、3.0以降は列挙クラスThreadModeによって応答イベントのスレッドを指定します.そのコードは次のとおりです.
public enum ThreadMode {

    //     ,       post        ,         
    POSTING

    //    (UI  )       ,         ,     Android      
    MAIN,

    //           ,              。           ,            
    BACKGROUND,


    //           ,           。                   。EventBus               
    ASYNC

}

4.イベントを送信します.postメソッドによりイベントの送信を行うことができ,送信イベントを登録したすべてのメソッドが応答する.
EventBus.getDefault().post(new MessageEvent("Hello everyone!"));

以上がEventBusの使用手順と簡単なコードの例であり,具体的な使い方にはすでに多くの資料があり,ここでは後述しない.
二、EventBusソース分析
以前EventBusについて勉強していたが、EventBusのソースコードの分析に関する文章もいくつか見たが、ほとんどの文章が早く、原理は変わらないが、現在のバージョン3.0.0では多くのコードが変化していることが分かったので、ここでは最新の3.0.0ソースコードに基づいて分析する.
1.EventBusインスタンスの作成
上記のコードでは、EventBus.getDefault()メソッドによってインスタンスを取得しています.そのソースコードは、典型的な単一のモードによって実現され、二重ロックによってスレッドの安全性と効率性が保証されることがわかります.
public static EventBus getDefault() {
     if (defaultInstance == null) {
         synchronized (EventBus.class) {
             if (defaultInstance == null) {
                 defaultInstance = new EventBus();
             }
         }
     }
     return defaultInstance;
 }

2.EventBus登録プロセス分析
register()メソッドで登録します.前の記事では4つの異なる登録メソッドがありますが、3.0.0では1つのregisterメソッドしかありません.コードは以下の通りです.
public void register(Object subscriber) {
      //           Activity、Fragment、Service    Class  
      Class> subscriberClass = subscriber.getClass();
      //  Class            Event      
      List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
      synchronized (this) {
          //      ,  subscribe Event        
          for (SubscriberMethod subscriberMethod : subscriberMethods) {
              subscribe(subscriber, subscriberMethod);
          }
      }
  }

上記の単純なコードからregisterプロセスは主に2つのステップであることがわかる:1.findSubscriberMethodsを呼び出してコンポーネント内のEvent応答方法の集合を取得する;2.コレクションを巡ってsubscribe()メソッドを呼び出して格納します.次に、上記の2つのステップの実装手順を見てみましょう.
(1)メソッド集合の取得
List findSubscriberMethods(Class> subscriberClass) {
       //1.            ,       
       List subscriberMethods = METHOD_CACHE.get(subscriberClass);
       if (subscriberMethods != null) {
           return subscriberMethods;
       }

      //          findUsingReflectionInSingleClass     ,     findUsingReflectionInSingleClass()  
       if (ignoreGeneratedIndex) {
           subscriberMethods = findUsingReflection(subscriberClass);
       } else {
           subscriberMethods = findUsingInfo(subscriberClass);
       }
       //                Event         ,               
       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;
       }
   }

findUsingReflectionInSingleClass()コード(ここでは、重要でない例外放出コードを省略します):
private void findUsingReflectionInSingleClass(FindState findState) {
     Method[] methods;
     try {
         // This is faster than getMethods, especially when subscribers are fat classes like Activities
         //          ,        ,         
         methods = findState.clazz.getDeclaredMethods();
     } catch (Throwable th) {
         // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
         methods = findState.clazz.getMethods();
         findState.skipSuperClasses = true;
     }
     //      
     for (Method method : methods) {
         //        
         int modifiers = method.getModifiers();
         //Event     public   static、abstract
         if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
             //           ,  Event           ,      ,              
             Class>[] parameterTypes = method.getParameterTypes();
             if (parameterTypes.length == 1) {
                //  @Subscribe    
                 Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                 //    null   Event    
                 if (subscribeAnnotation != null){
                     //        Event  Class  
                     Class> eventType = parameterTypes[0];
                     if (findState.checkAdd(method, eventType)) {
                         //       Event         
                         ThreadMode threadMode = subscribeAnnotation.threadMode();
                         //        SubscriberMethod  
                         //SubscriberMethod          Method  、    、    、      
                         //     FindState subscriberMethods     ,        
                         findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                 subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                     }
                 }
             }             
         }
     }
 }

ソースコードを解析することにより、コンポーネント内のすべてのメソッドを巡回し、サブスクリプションルールに適合するメソッドをSubscriberMethodオブジェクトとしてカプセル化して格納することが明らかになります.
(2)格納方法の集合
Event応答メソッドを取得する手順を大まかに分析し、subscribeメソッドが応答メソッドをどのように格納しているかを分析します.
  // Must be called in synchronized block         
   private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
       //      Event      Event  Class  
       Class> eventType = subscriberMethod.eventType;
       //      Class       Event  Class        Subscription  
       Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
       //     Event    Subscription    ,              subscriptionsByEventType   
       CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
       if (subscriptions == null) {
           subscriptions = new CopyOnWriteArrayList<>();
           subscriptionsByEventType.put(eventType, subscriptions);
       } else {
           //            Subscription                ,    
           if (subscriptions.contains(newSubscription)) {
               throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                       + eventType);
           }
       }

       int size = subscriptions.size();
       //           Subscription      
       for (int i = 0; i <= size; i++) {
           if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
               subscriptions.add(i, newSubscription);
               break;
           }
       }
       //               Event    ,    Event        
       List> subscribedEvents = typesBySubscriber.get(subscriber);
       if (subscribedEvents == null) {
           subscribedEvents = new ArrayList<>();
           typesBySubscriber.put(subscriber, subscribedEvents);
       }
       subscribedEvents.add(eventType);
       //    ,      
       if (subscriberMethod.sticky) {
           if (eventInheritance) {
               // Existing sticky events of all subclasses of eventType have to be considered.
               // Note: Iterating over all events may be inefficient with lots of sticky events,
               // thus data structure should be changed to allow a more efficient lookup
               // (e.g. an additional map storing sub classes of super classes: Class -> List).
               Set, Object>> entries = stickyEvents.entrySet();
               for (Map.Entry, 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);
           }
       }
   }

上はストレージ・プロシージャ全体であり、registerプロシージャ全体が終了します.結果として,登録されたコンポーネントオブジェクトをキーとし,Eventタイプを値とするコンポーネントに基づいてそのリスニングイベントタイプを格納するtypesBySubscriber集合とEventのClassをキーとし,Subscriptionオブジェクトを値とするEventを格納する登録者と応答方法のsubscriptionsByEventType集合の2つのMap集合が作成された.
3.EventBus配布イベントフロー分析
イベントの配布はpostメソッドで始まることを知っています.次にpostのコードを見てみましょう.
PostingThreadStateは現在のpostスレッドの基本情報をカプセル化し,ThreadLocalにより格納する.ThreadLocalはスレッドをキーとしてデータを格納することができ、よく知らない子供靴は技術小黒屋のこの文章を参考にすることができます.http://droidyue.com/blog/2016/03/13/learning-threadlocal-in-java/
public void post(Object event) {
        //          
        PostingThreadState postingState = currentPostingThreadState.get();
        List eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            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;
            }
        }
    }

上記のpostメソッドは、postSingleEvent()などの一連のメソッドを呼び出すことによって最終的にpostToSubscription()に呼び出され、ThreadMode情報に基づいてEventが配布される
private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
       switch (subscription.subscriberMethod.threadMode) {
           //  ,          
           case POSTING:
               invokeSubscriber(subscription, event);
               break;
           //      ,    ;    ,  mainThreadPoster(    Handler)        
           case MAIN:
               if (isMainThread) {
                   invokeSubscriber(subscription, event);
               } else {
                   mainThreadPoster.enqueue(subscription, event);
               }
               break;
               //       ,   backgroundPoster      ;            
           case BACKGROUND:
               if (isMainThread) {
                   backgroundPoster.enqueue(subscription, event);
               } else {
                   invokeSubscriber(subscription, event);
               }
               break;
           case ASYNC://        ,    asyncPoster             
               asyncPoster.enqueue(subscription, event);
               break;
           default:
               throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
       }
   }

   //            invokeSubscriber()  ,               Event    
   void invokeSubscriber(Subscription subscription, Object event) {
       try {
          //       ,            
           subscription.subscriberMethod.method.invoke(subscription.subscriber, event);
       } catch (InvocationTargetException e) {
           handleSubscriberException(subscription, event, e.getCause());
       } catch (IllegalAccessException e) {
           throw new IllegalStateException("Unexpected exception", e);
       }
   }

スレッドを切り替える必要がない方法で呼び出すのは簡単ですが、後述しません.次に,MainThreadを例にEventBusがスレッドをどのように切り替えるかを検討する.メインスレッドはmainThreadPosterオブジェクトで切り替えられます.Handlerクラスです.次はそのコードです.
final class HandlerPoster extends Handler {

    private final PendingPostQueue queue;
    private final int maxMillisInsideHandleMessage;
    private final EventBus eventBus;
    private boolean handlerActive;

    HandlerPoster(EventBus eventBus, Looper looper, int maxMillisInsideHandleMessage) {
        super(looper);
        this.eventBus = eventBus;
        this.maxMillisInsideHandleMessage = maxMillisInsideHandleMessage;
        queue = new PendingPostQueue();
    }
    //  mainThreadPoster enqueue  ,   Subscription Event     PendingPost       
    //PendingPostQueue         ,    Message  handleMessage   
    void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        synchronized (this) {
            queue.enqueue(pendingPost);
            if (!handlerActive) {
                handlerActive = true;
                if (!sendMessage(obtainMessage())) {
                    throw new EventBusException("Could not send handler message");
                }
            }
        }
    }

    @Override
    public void handleMessage(Message msg) {
        boolean rescheduled = false;
        try {
            long started = SystemClock.uptimeMillis();
            while (true) {//           
                //              
                PendingPost pendingPost = queue.poll();
                //        ,                
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        //                        
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            handlerActive = false;
                            return;
                        }
                    }
                }
                //        invokeSubscriber          
                //      invokeSubscriber(Subscription subscription, Object event)    
                eventBus.invokeSubscriber(pendingPost);
                long timeInMethod = SystemClock.uptimeMillis() - started;
                if (timeInMethod >= maxMillisInsideHandleMessage) {//               
                    if (!sendMessage(obtainMessage())) {
                        throw new EventBusException("Could not send handler message");
                    }
                    rescheduled = true;
                    return;
                }
            }
        } finally {
            handlerActive = rescheduled;
        }
    }
}

簡単に言えば、ThreadModeに従って対応するHandlerクラスを呼び出してスレッドの切り替えを行い、反射によってEvent応答方法を実行する.上はEventBus全体の登録とpostプロセスです.ここには登録と配布の過程を示す2枚の図が描かれており、必要な子供靴に役立つことを望んでいます.