EventBus后续深入知识点整理

DavSlb 8年前
   <p>根据上一篇文章 <a href="http://www.open-open.com/lib/view/open1464132609918.html">浅析EventBus 3.0实现思想</a> 对EventBus的概括,本文针对其中一些重要且比较有意思的知识点,做一下如下的汇总整理 :</p>    <h2>FindState的妙用</h2>    <p>在EventBus中,会根据class信息,来获取 SubscriberMethod ,这里会在 SubscriberMethodFinder 中进行处理,提供了两种方式来进行获取:</p>    <ul>     <li>通过 findUsingInfo(Class<?> subscriberClass) 在apt中进行查找获取</li>     <li>使用’findUsingReflection(Class<?> subscriberClass)‘方法,进行反射来获取 而在这里,EventBus采用了一个中间器 FindState ,来看一下它的结构:</li>    </ul>    <pre>  <code class="language-java">static class FindState {      final List<SubscriberMethod> subscriberMethods = new ArrayList<>();      Class<?> subscriberClass;      Class<?> clazz;      boolean skipSuperClasses;      SubscriberInfo subscriberInfo;  }</code></pre>    <p>这里对查找的状态值做了一些封装,其中有订阅类 subscriberClass ,事件对象 clazz ,以及查找的结果 subscriberMethods 、 subscriberInfo 等,另外,还有一个判断的标志量 skipSuperClasses ,用来标记是否需要进行父类的查看查找。</p>    <p>同时,我们可以看出在使用EventBus定义订阅方法的时候,有些通用的逻辑,是可以抽象放置在父类中的。</p>    <p>为什么要使用FindState呢?首先是面向对象封装的采用,那么看看它给我们提供了哪些方法?</p>    <pre>  <code class="language-java">void initForSubscriber(Class<?> subscriberClass) {      ...  }    boolean checkAdd(Method method, Class<?> eventType) {      ...  }    private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {      ...  }    void moveToSuperclass() {      ...  }</code></pre>    <p>方法中的 initForSubscriber 是用来初始化传入订阅类的,两个check方法则是用来检查方法信息的,这样用来保证获取的订阅方法都是合法的。 moveToSuperClass 则是需要查看父类中的订阅方法。这样对方法检查的逻辑,我们就把它们抽象在了FindState中。</p>    <h2>缓存的使用</h2>    <p>使用java的,应该要知道频繁地创建对象,是非常消耗资源的,在jvm垃圾回收时候,会出现内存抖动的问题。所以,我们在这里,一定要注意缓存的使用。</p>    <p>上文中提到的中间器FindState,就采用了缓存:</p>    <pre>  <code class="language-java">private static final int POOL_SIZE = 4;  private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE]; </code></pre>    <p>指定了FindState的缓存大小为4,并使用一维的静态数组,所以这里需要注意线程同步的问题:</p>    <pre>  <code class="language-java">private FindState prepareFindState() {      synchronized (FIND_STATE_POOL) {          for (int i = 0; i < POOL_SIZE; i++) {              FindState state = FIND_STATE_POOL[i];              if (state != null) {                  FIND_STATE_POOL[i] = null;                  return state;              }          }      }      return new FindState();  }</code></pre>    <p>这段是用来获取FindState, 可以看到的是对这段缓存的获取使用了 synchronized 关键字,来将缓存中FindState的获取,变为同步块。 而在subscriberMethod的获取的同时,则对FindState的缓存做了添加的操作,同样是也必须是同步代码块:</p>    <pre>  <code class="language-java">private List<SubscriberMethod> getMethodsAndRelease(FindState findState) {       List<SubscriberMethod> subscriberMethods = new ArrayList<>(findState.subscriberMethods);       findState.recycle();       synchronized (FIND_STATE_POOL) {           for (int i = 0; i < POOL_SIZE; i++) {               if (FIND_STATE_POOL[i] == null) {                   FIND_STATE_POOL[i] = findState;                   break;               }           }       }       return subscriberMethods;   }</code></pre>    <p>另外,EventBus也对subsciberMethod的获取,也做了缓存的操作,这样进行SubscriberMethod查找的时候,则优先进行缓存的查找:</p>    <pre>  <code class="language-java">private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); </code></pre>    <p>这里,使用的是数据结构是 ConcurrentHashMap ,就可以不必写大量的同步代码块了。</p>    <h2>反射类方法的使用</h2>    <p>反射虽然是比较浪费性能的,但对我们Java开发者来说,这又是必须掌握的一个技能,现在来熟悉一下EventBus中通过 @Subscribe 注解对 SubscriberMethod 的查找:</p>    <pre>  <code class="language-java">private void findUsingReflectionInSingleClass(FindState findState) {       Method[] methods;       // 优先使用getDeclareMethods方法,如注释中所说,比getMethods方法块。       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();           // 通过访问符只获取public           if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {               Class<?>[] parameterTypes = method.getParameterTypes();               // 方法的参数(事件类型)长度只能为1               if (parameterTypes.length == 1) {                   Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);                   if (subscribeAnnotation != null) {                       Class<?> eventType = parameterTypes[0];                       // 获取到annotation中的内容,进行subscriberMethod的添加                       if (findState.checkAdd(method, eventType)) {                           ThreadMode threadMode = subscribeAnnotation.threadMode();                           findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,                                   subscribeAnnotation.priority(), subscribeAnnotation.sticky()));                       }                   }               } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {                   //抛出方法参数只能为1的异常                   String methodName = method.getDeclaringClass().getName() + "." + method.getName();                   throw new EventBusException("@Subscribe method " + methodName +                           "must have exactly 1 parameter but has " + parameterTypes.length);               }           } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {               //抛出方法访问符只能为public的异常               String methodName = method.getDeclaringClass().getName() + "." + method.getName();               throw new EventBusException(methodName +                       " is a illegal @Subscribe method: must be public, non-static, and non-abstract");           }       }   }</code></pre>    <p>其中,最核心的类便是 Method 和 Class ,通过 Class 的 getDeclaredMethods 及 getMethods 来进行方法信息的获取;使用 Method 类的 getParameterTypes 获取方法的参数及 getAnnotation 获取方法的注解类。</p>    <h2>线程处理类信息的使用</h2>    <p>在EventBus类中,定义了4种线程处理的策略:</p>    <pre>  <code class="language-java">public enum ThreadMode {  POSTING,  MAIN,  BACKGROUND,  ASYNC } </code></pre>    <p>POSTING 采用与事件发布者相同的线程, MAIN 指定为主线程, BACKGROUND 指定为后台线程,而 ASYNC 相比前三者不同的地方是可以处理耗时的操作,其采用了线程池,且是一个异步执行的过程,即事件的订阅者可以立即得到执行。</p>    <p>这里,我们主要看两个Poster, BackgroundPoster 和 AsyncPoster :</p>    <h3>BackgroundPoster – 后台任务执行</h3>    <pre>  <code class="language-java">final class BackgroundPoster implements Runnable {        private final PendingPostQueue queue;      private final EventBus eventBus;        private volatile boolean executorRunning;        BackgroundPoster(EventBus eventBus) {          this.eventBus = eventBus;          queue = new PendingPostQueue();      }        public void enqueue(Subscription subscription, Object event) {          PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);          synchronized (this) {              queue.enqueue(pendingPost);              if (!executorRunning) {                  executorRunning = true;                  eventBus.getExecutorService().execute(this);              }          }      }        @Override      public void run() {          try {              try {                  while (true) {                      PendingPost pendingPost = queue.poll(1000);                      if (pendingPost == null) {                          synchronized (this) {                              // Check again, this time in synchronized                              pendingPost = queue.poll();                              if (pendingPost == null) {                                  executorRunning = false;                                  return;                              }                          }                      }                      eventBus.invokeSubscriber(pendingPost);                  }              } catch (InterruptedException e) {                  Log.w("Event", Thread.currentThread().getName() + " was interruppted", e);              }          } finally {              executorRunning = false;          }      }    }</code></pre>    <p>代码中,主要通过 enqueue 方法,将当前的订阅者添加至队列 PendingPostQueue 中,是否立即执行,则需要判断当前队列是否还有正在执行的任务,若没有的话,则立即执行,若还有执行任务的话,则只进行队列的添加。这样,保证了后台任务永远只会在一个线程执行。</p>    <h3>AsyncPoster – 异步任务执行</h3>    <pre>  <code class="language-java">class AsyncPoster implements Runnable {        private final PendingPostQueue queue;      private final EventBus eventBus;        AsyncPoster(EventBus eventBus) {          this.eventBus = eventBus;          queue = new PendingPostQueue();      }        public void enqueue(Subscription subscription, Object event) {          PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);          queue.enqueue(pendingPost);          eventBus.getExecutorService().execute(this);      }        @Override      public void run() {          PendingPost pendingPost = queue.poll();          if(pendingPost == null) {              throw new IllegalStateException("No pending post available");          }          eventBus.invokeSubscriber(pendingPost);      }    }</code></pre>    <p>这段代码就很简单了,直接通过线程池调用执行,相比 BackgroundPoster 执行来说,则没有等待的过程。</p>    <h2>事件执行队列 PendingPostQueue</h2>    <p>EventBus对事件的执行,采用队列的数据结构:</p>    <pre>  <code class="language-java">final class PendingPostQueue {      private PendingPost head;      private PendingPost tail;        synchronized void enqueue(PendingPost pendingPost) {          if (pendingPost == null) {              throw new NullPointerException("null cannot be enqueued");          }          if (tail != null) {              tail.next = pendingPost;              tail = pendingPost;          } else if (head == null) {              head = tail = pendingPost;          } else {              throw new IllegalStateException("Head present, but no tail");          }          notifyAll();      }        synchronized PendingPost poll() {          PendingPost pendingPost = head;          if (head != null) {              head = head.next;              if (head == null) {                  tail = null;              }          }          return pendingPost;      }        synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {          if (head == null) {              wait(maxMillisToWait);          }          return poll();      }    }</code></pre>    <p>而对 PendingPost 的封装,使用了数据缓存池:</p>    <pre>  <code class="language-java">final class PendingPost {      private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();        Object event;      Subscription subscription;      PendingPost next;       // 对PendingPost的获取,优先从缓存池中拿      private PendingPost(Object event, Subscription subscription) {          this.event = event;          this.subscription = subscription;      }        static PendingPost obtainPendingPost(Subscription subscription, Object event) {          synchronized (pendingPostPool) {              int size = pendingPostPool.size();              if (size > 0) {                  PendingPost pendingPost = pendingPostPool.remove(size - 1);                  pendingPost.event = event;                  pendingPost.subscription = subscription;                  pendingPost.next = null;                  return pendingPost;              }          }          return new PendingPost(event, subscription);      }        // 对PendingPost释放时,将其添加到缓存池中      static void releasePendingPost(PendingPost pendingPost) {          pendingPost.event = null;          pendingPost.subscription = null;          pendingPost.next = null;          synchronized (pendingPostPool) {              // Don't let the pool grow indefinitely              if (pendingPostPool.size() < 10000) {                  pendingPostPool.add(pendingPost);              }          }      }    }</code></pre>    <p>可以看到其对缓存的大小限制到10000,好任性啊。。</p>    <h2>总结</h2>    <p>EventBus给我们提供了相当强大的功能,同时它的写法也相当有味道,值得我们深深地去研究。总的来说,其中EventBus采用了Facade模式,方便开发者的统一调用;另外不同的线程策略,以及反射代码,Apt处理代码生成以及缓存的大量使用。</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959673674256775723" rel="nofollow">http://alighters.com/blog/2016/05/24/eventbus-3-dot-0-indepth-knowledge/</a></p>    <p> </p>