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,以及查找的结果subscriberMethodssubscriberInfo等,另外,还有一个判断的标志量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");
         }
     }
 }

其中,最核心的类便是MethodClass,通过ClassgetDeclaredMethodsgetMethods来进行方法信息的获取;使用Method类的getParameterTypes获取方法的参数及getAnnotation获取方法的注解类。

线程处理类信息的使用

在EventBus类中,定义了4种线程处理的策略:

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

POSTING采用与事件发布者相同的线程,MAIN指定为主线程,BACKGROUND指定为后台线程,而ASYNC相比前三者不同的地方是可以处理耗时的操作,其采用了线程池,且是一个异步执行的过程,即事件的订阅者可以立即得到执行。

这里,我们主要看两个Poster, BackgroundPosterAsyncPoster

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/

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,380评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,580评论 18 139
  • 最近在项目中使用了EventBus(3.0),觉得非常好用,于是就看了一些关于EventBus源码分析的文章,现在...
    shenhuniurou阅读 1,493评论 0 4
  • 今晚休息前,看着电视,忽然想起今天是15日了,坏了,14日作业雨还未交作业。再过半小时不交作业就彻底是缺失本次作业...
    李瑞丽阅读 152评论 0 0
  • Halloween it's an autumn fastival in the UK,We wearing sc...
    5505王妙伊阅读 263评论 0 1