EventBus后续深入知识点整理
根据上一篇文章浅析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/