[搞定开源05] EventBus3.1.1原理

Android开源项目原理系列
[搞定开源01] okhttp 3.10原理
[搞定开源02] okio 1.14原理
[搞定开源03] retrofit 2.4.0与设计模式
[搞定开源04] 手动实现RxJava2线程切换

EventBus是Android的发布订阅开源库,优势和使用方法不用说了,简单过一下源码,了解核心原理。

当前EventBus最新版本是3.1.1,和前代相比,方法名可以自定义,新增注解支持。

EventBus构造函数

优秀开源库的基本特征是,常用功能用一个入口类就可以玩转,EventBus也是。

public static EventBus getDefault() {
  if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

获取EventBus对象常规使用getDefault,典型的Double Check获取单例。如果有特别需求,可以通过EventBusBuilder,典型的建造者模式。

EventBus构造函数创建了十多个对象,关注其中的:

EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    //...
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    //...
}

Log

EventBus的日志打印使用Logger接口,已经实现了:

  • AndroidLogger
  • JavaLogger
  • SystemOutLogger
static final boolean ANDROID_LOG_AVAILABLE;

static {
    boolean android = false;
    try {
        android = Class.forName("android.util.Log") != null;
    } catch (ClassNotFoundException e) {
        // OK
    }
    ANDROID_LOG_AVAILABLE = android;
}

public static boolean isAndroidLogAvailable() {
    return ANDROID_LOG_AVAILABLE;
}

对于Android,直接尝试获取Log类,同时用来判断当前是否Android。

获取Android的UI线程

EventBus如何让事件执行在Android的UI线程呢?

MainThreadSupport getMainThreadSupport() {
    if (mainThreadSupport != null) {
        return mainThreadSupport;
    } else if (Logger.AndroidLogger.isAndroidLogAvailable()) {
        Object looperOrNull = getAndroidMainLooperOrNull();
        return looperOrNull == null ? null :
                new MainThreadSupport.AndroidHandlerMainThreadSupport((Looper) looperOrNull);
    } else {
        return null;
    }
}

Object getAndroidMainLooperOrNull() {
    try {
        return Looper.getMainLooper();
    } catch (RuntimeException e) {
        // Not really a functional Android (e.g. "Stub!" maven dependencies)
        return null;
    }
}

这里用到了上面判断Android的方法,如果是,创建AndroidHandlerMainThreadSupport,保存了Looper.getMainLooper()。很熟悉吧,这就是Android的UI线程Looper。

Poster

EventBus的event可以指定运行在UI线程或者后台线程,靠的就是Poster,下文会说到,透露一丢丢:

  • UI线程:MainLooper
  • 后台线程:线程池

注册/取消注册

想要接收EventBus发送的事件,对象必须先注册,停止接收时取消注册。注册过程,本质是通知EventBus查找对象中的订阅方法。

Subscribe注解

@Subscribe(threadMode = ThreadMode.MAIN)
public void onMessageEvent4Main(MsgEvent event) {}

EventBus3之后,订阅方法名改为自定义,通过注解描述。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface Subscribe {
    ThreadMode threadMode() default ThreadMode.POSTING;
    boolean sticky() default false;
    int priority() default 0;
}

除了基础的ThreadMode线程模型,还可以定义粘性事件和优先级。

注解信息,在注册过程最后由SubscriberMethod类描述。

注册过程

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

注册时传入订阅对象,两部操作:

  1. 分析订阅对象的类,SubscriberMethodFinder从中查找订阅方法,包装为SubscriberMethod。
  2. 调用subscribe,数据转化为EventBus内部的两个Map。
List<SubscriberMethod>
List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    //1
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    //2
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        //3
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}
  1. 查找目标类的订阅方法前,先查找缓存METHOD_CACHE,用的是ConcurrentHashMap,因为要处理多线程。
Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>()
  1. 默认创建的EventBus对象ignoreGeneratedIndex=false,先看findUsingInfo
  2. 获取List成功后,保存在缓存METHOD_CACHE
SubscriberMethodFinder.FindState

上面第二步重点是使用SubscriberMethodFinder的内部类FindState执行查找过程:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
    //1
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    //2
    while (findState.clazz != null) {
        findState.subscriberInfo = getSubscriberInfo(findState);
        if (findState.subscriberInfo != null) {
            SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();
            for (SubscriberMethod subscriberMethod : array) {
                if (findState.checkAdd(subscriberMethod.method, subscriberMethod.eventType)) {
                    findState.subscriberMethods.add(subscriberMethod);
                }
            }
        } else {
            //3
            findUsingReflectionInSingleClass(findState);
        }
        //4
        findState.moveToSuperclass();
    }
    //5
    return getMethodsAndRelease(findState);
}

第一步是调用prepareFindState获取FindState:

private static final int POOL_SIZE = 4;
private static final FindState[] FIND_STATE_POOL = new FindState[POOL_SIZE];
    
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的创建不是直接new,而是尝试从FIND_STATE_POOL中获取(最多保存4个),目测是享元模式,没有才new对象。

第二步进入循环,从当前类开始,遍历父类查找订阅方法。

这里涉及EventBus3新特性,运行时反射查找方法当然会慢点,可以使用Subscriber Index在编译期操作加速,具体不展开,直接看else部分。

第三步的findUsingReflectionInSingleClass,使用反射查找,细节不用展开。

第四步将当前查找目标切换到父类,继续循环查找。

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

第五步清理FindState,放回FIND_STATE_POOL,下次继续利用。

subscribe

register函数对每个SubscriberMethod执行subscribe,注意这里synchronized当前EventBus对象。

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions == null) {
        subscriptions = new CopyOnWriteArrayList<>();
        subscriptionsByEventType.put(eventType, subscriptions);
    } else {
        if (subscriptions.contains(newSubscription)) {
            throw new EventBusException("Subscriber " + subscriber.getClass() + " already registered to event "
                    + eventType);
        }
    }

    int size = subscriptions.size();
    for (int i = 0; i <= size; i++) {
        if (i == size || subscriberMethod.priority > subscriptions.get(i).subscriberMethod.priority) {
            subscriptions.add(i, newSubscription);
            break;
        }
    }

    List<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);
    if (subscribedEvents == null) {
        subscribedEvents = new ArrayList<>();
        typesBySubscriber.put(subscriber, subscribedEvents);
    }
    subscribedEvents.add(eventType);

    //... 粘性事件略
}

subscribe不外乎是对数据的挪腾,目的是构建subscriptionsByEventType和typesBySubscriber两个Map:

subscriptionsByEventType

private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;

Subscription包装了订阅对象和订阅方法,因此Map的key/value对应如下:

event类型 -> List<订阅对象,订阅方法>

表示了event由哪些对象的哪个方法订阅了。

typesBySubscriber

private final Map<Object, List<Class<?>>> typesBySubscriber;

typesBySubscriber简单些,表示订阅对象订阅了哪些event类型:

订阅对象 -> List<event类型>

取消注册

public synchronized void unregister(Object subscriber) {
    List<Class<?>> subscribedTypes = typesBySubscriber.get(subscriber);
    if (subscribedTypes != null) {
        for (Class<?> eventType : subscribedTypes) {
            unsubscribeByEventType(subscriber, eventType);
        }
        typesBySubscriber.remove(subscriber);
    } else {
        logger.log(Level.WARNING, "Subscriber to unregister was not registered before: " + subscriber.getClass());
    }
}

取消注册的逻辑很简单,将当前订阅者的信息从subscriptionsByEventType和typesBySubscriber中移除。

发送

PostingThreadState(每个线程一份)

EventBus发送event调用post方法,首先来认识EventBus核心的内部类PostingThreadState。

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};
    
final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

PostingThreadState包含eventQueue和一些标志变量,通过ThreadLocal在每个线程中维护一个对象(ThreadLocal的initialValue方法初始创建PostingThreadState对象)。

没有深奥的玄妙,看数据结构大概可以知道,EventBus发送原理是将event入队在线程上的PostingThreadState,再查找订阅者发送。

event进出队

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    eventQueue.add(event);

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

发送event的逻辑很明确,取出当前线程的PostingThreadState,将event入队。当不在发送状态时,循环调用postSingleEvent发送所有event。

发送event

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    //1
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
        List<Class<?>> eventTypes = lookupAllEventTypes(eventClass);
        int countTypes = eventTypes.size();
        for (int h = 0; h < countTypes; h++) {
            Class<?> clazz = eventTypes.get(h);
            subscriptionFound |= postSingleEventForEventType(event, postingState, clazz);
        }
    } else {
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    //2
    if (!subscriptionFound) {
        if (logNoSubscriberMessages) {
            logger.log(Level.FINE, "No subscribers registered for event " + eventClass);
        }
        if (sendNoSubscriberEvent && eventClass != NoSubscriberEvent.class &&
                eventClass != SubscriberExceptionEvent.class) {
            post(new NoSubscriberEvent(this, event));
        }
    }
}

第一部分先收集event的类型,eventInheritance在EventBus默认构造函数里为true,表示会向上遍历event的父类。然后调用postSingleEventForEventType执行发送过程,得到发送结果subscriptionFound。

第二部分,如果找不到订阅者,EventBus将会发送NoSubscriberEvent这个事件,业务上可以接收处理。

private boolean postSingleEventForEventType(Object event, PostingThreadState postingState, Class<?> eventClass) {
    CopyOnWriteArrayList<Subscription> subscriptions;
    synchronized (this) {
        subscriptions = subscriptionsByEventType.get(eventClass);
    }
    if (subscriptions != null && !subscriptions.isEmpty()) {
        for (Subscription subscription : subscriptions) {
            postingState.event = event;
            postingState.subscription = subscription;
            boolean aborted = false;
            try {
                postToSubscription(subscription, event, postingState.isMainThread);
                aborted = postingState.canceled;
            } finally {
                postingState.event = null;
                postingState.subscription = null;
                postingState.canceled = false;
            }
            if (aborted) {
                break;
            }
        }
        return true;
    }
    return false;
}

postSingleEventForEventType根据event类型从subscriptionsByEventType获取所有订阅者,对每个订阅者执行postToSubscription。

订阅方法各有执行线程的要求,postToSubscription其实就是对各个线程模型的分开处理,下面分析常用的POSTING、MAIN和BACKGROUND。

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

POSTING,在哪个线程发送,就在哪个线程处理事件,直接反射invoke。

MAIN
 if (isMainThread) {
    invokeSubscriber(subscription, event);
} else {
    mainThreadPoster.enqueue(subscription, event);
}

如果当前已经在UI线程,直接调用invokeSubscriber,否则调用mainThreadPoster将event入队。

前面分析了AndroidHandlerMainThreadSupport保存了UI线程的Looper,通过Looper创建HandlerPoster,mainThreadPoster正是HandlerPoster的对象。有了UI线程的Handler,接下来的都是顺理成章的代码。

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

订阅者信息Subscription和event包装为PendingPost,通过next字段,多个PendingPost构建队列PendingPostQueue。

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

通过发送空message,切换线程到UI线程,从队列中取出event调用invokeSubscriber。注意到,在UI线程有执行时长限制,超时会抛出异常。

BACKGROUND
if (isMainThread) {
    backgroundPoster.enqueue(subscription, event);
} else {
    invokeSubscriber(subscription, event);
}
  • 如果事件在UI线程中发布,那么该事件就会在新的线程中运行
  • 如果事件在子线程中发布,那么该事件直接在发布事件的线程中执行

BackgroundPoster处理事件在新线程中的运行,同样的创建PendingPost并入队。

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

线程池从EventBus对象获取,看定义,是一个线程数几乎无限制的CachedThreadPool。

private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

最终在线程池的执行过程也是PendingPost出队,调用invokeSubscriber。(代码嵌套很深!)

@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) {
            eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
        }
    } finally {
        executorRunning = false;
    }
}

后记

EventBus源码没有太难的地方,快速通过。

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