EventBus后續深入知識點整理

DavSlb 8年前發布 | 5K 次閱讀 EventBus Android開發 移動開發

根據上一篇文章 淺析EventBus 3.0實現思想 對EventBus的概括,本文針對其中一些重要且比較有意思的知識點,做一下如下的匯總整理 :

FindState的妙用

在EventBus中,會根據class信息,來獲取 SubscriberMethod ,這里會在 SubscriberMethodFinder 中進行處理,提供了兩種方式來進行獲取:

  • 通過 findUsingInfo(Class<?> subscriberClass) 在apt中進行查找獲取
  • 使用’findUsingReflection(Class<?> subscriberClass)‘方法,進行反射來獲取 而在這里,EventBus采用了一個中間器 FindState ,來看一下它的結構:
static class FindState {
    final List<SubscriberMethod> subscriberMethods = new ArrayList<>();
    Class<?> subscriberClass;
    Class<?> clazz;
    boolean skipSuperClasses;
    SubscriberInfo subscriberInfo;
}

這里對查找的狀態值做了一些封裝,其中有訂閱類 subscriberClass ,事件對象 clazz ,以及查找的結果 subscriberMethods 、 subscriberInfo 等,另外,還有一個判斷的標志量 skipSuperClasses ,用來標記是否需要進行父類的查看查找。

同時,我們可以看出在使用EventBus定義訂閱方法的時候,有些通用的邏輯,是可以抽象放置在父類中的。

為什么要使用FindState呢?首先是面向對象封裝的采用,那么看看它給我們提供了哪些方法?

void initForSubscriber(Class<?> subscriberClass) {
    ...
}

boolean checkAdd(Method method, Class<?> eventType) {
    ...
}

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    ...
}

void moveToSuperclass() {
    ...
}

方法中的 initForSubscriber 是用來初始化傳入訂閱類的,兩個check方法則是用來檢查方法信息的,這樣用來保證獲取的訂閱方法都是合法的。 moveToSuperClass 則是需要查看父類中的訂閱方法。這樣對方法檢查的邏輯,我們就把它們抽象在了FindState中。

緩存的使用

使用java的,應該要知道頻繁地創建對象,是非常消耗資源的,在jvm垃圾回收時候,會出現內存抖動的問題。所以,我們在這里,一定要注意緩存的使用。

上文中提到的中間器FindState,就采用了緩存:

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE]; 

指定了FindState的緩存大小為4,并使用一維的靜態數組,所以這里需要注意線程同步的問題:

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();
}

這段是用來獲取FindState, 可以看到的是對這段緩存的獲取使用了 synchronized 關鍵字,來將緩存中FindState的獲取,變為同步塊。 而在subscriberMethod的獲取的同時,則對FindState的緩存做了添加的操作,同樣是也必須是同步代碼塊:

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;
 }

另外,EventBus也對subsciberMethod的獲取,也做了緩存的操作,這樣進行SubscriberMethod查找的時候,則優先進行緩存的查找:

private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>(); 

這里,使用的是數據結構是 ConcurrentHashMap ,就可以不必寫大量的同步代碼塊了。

反射類方法的使用

反射雖然是比較浪費性能的,但對我們Java開發者來說,這又是必須掌握的一個技能,現在來熟悉一下EventBus中通過 @Subscribe 注解對 SubscriberMethod 的查找:

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");
         }
     }
 }

其中,最核心的類便是 Method 和 Class ,通過 Class 的 getDeclaredMethods 及 getMethods 來進行方法信息的獲取;使用 Method 類的 getParameterTypes 獲取方法的參數及 getAnnotation 獲取方法的注解類。

線程處理類信息的使用

在EventBus類中,定義了4種線程處理的策略:

public enum ThreadMode {  POSTING,  MAIN,  BACKGROUND,  ASYNC } 

POSTING 采用與事件發布者相同的線程, MAIN 指定為主線程, BACKGROUND 指定為后臺線程,而 ASYNC 相比前三者不同的地方是可以處理耗時的操作,其采用了線程池,且是一個異步執行的過程,即事件的訂閱者可以立即得到執行。

這里,我們主要看兩個Poster, BackgroundPoster 和 AsyncPoster :

BackgroundPoster – 后臺任務執行

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;
        }
    }

}

代碼中,主要通過 enqueue 方法,將當前的訂閱者添加至隊列 PendingPostQueue 中,是否立即執行,則需要判斷當前隊列是否還有正在執行的任務,若沒有的話,則立即執行,若還有執行任務的話,則只進行隊列的添加。這樣,保證了后臺任務永遠只會在一個線程執行。

AsyncPoster – 異步任務執行

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);
    }

}

這段代碼就很簡單了,直接通過線程池調用執行,相比 BackgroundPoster 執行來說,則沒有等待的過程。

事件執行隊列 PendingPostQueue

EventBus對事件的執行,采用隊列的數據結構:

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();
    }

}

而對 PendingPost 的封裝,使用了數據緩存池:

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);
            }
        }
    }

}

可以看到其對緩存的大小限制到10000,好任性啊。。

總結

EventBus給我們提供了相當強大的功能,同時它的寫法也相當有味道,值得我們深深地去研究。總的來說,其中EventBus采用了Facade模式,方便開發者的統一調用;另外不同的線程策略,以及反射代碼,Apt處理代碼生成以及緩存的大量使用。

 

來自: http://alighters.com/blog/2016/05/24/eventbus-3-dot-0-indepth-knowledge/

 

 本文由用戶 DavSlb 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!