EventBus 3.0 使用及源码解析

1.为什么会诞生EventBus

在Android开发过程中,我们经常有这样的需求:位于栈顶的Activity需要关闭非栈顶的Activity或调用其中的方法(比如刷新数据,更新UI等)

例:现在有3个Activity,分别是:主界面(A),个人中心(B),头像裁剪&上传页面(C),在这三个页面中都有一个显示用户头像的ImageView。当用户进入C页面选择图片裁剪并上传至后台服务器后。我们要保证ABC三个页面的头像显示一致,那么我们就来看看不用EventBus怎么实现这样的功能。

普遍的做法大概就这三种,如果还有其他方法,欢迎在评论区留言。

  • 在AB两个Activity中暴露一个public static的方法,供C修改成功后调用。
  • 在AB两个Activity中注册广播,当C修改成功后,发送对应的广播,完成修改。
  • 通过startActivityForResult和onActivityResult来实现。

下面我们分析一下这三种方法的实现:

第一种:由于是静态方法,所以使用这种方法时,要将ImageView设置为全局静态变量,浪费宝贵的内存资源。

第二种:发送一个广播做通知,其实是一个不错的选择,发送广播然后实现广播类做回调通知。但是如果现在需求是对一个Fragment做通知。这个时候广播就派不上用场了。毕竟广播只能再Activity中注册发送广播,如果硬要这么做,又涉及到Fragment与Activity之间的数据交互问题。

第三种:代码量巨大并且逻辑有点绕,开发和维护成本都很大。

这三种方法,没有完全的对与错,好与坏,仁者见仁智者见智。

想想这些方法,真的难用啊,就没有方便快捷的方法吗?

没时间解释了,快上车吧

滴滴滴,就在这个时候,一辆EventBus缓缓驶来,你发现驾驶员居然是一位神仙(old driver)。一种全局的观察者模式,你可以把你的愿望(Event)告诉(Post)这位神仙(old driver),他有很多小老弟(多线程),就算竭尽全力(不费吹灰之力)也要让你的愿望在你指定的地方实现。

EventBus-Publish-Subscribe.png

2.下面我们看看EventBus怎么使用。

GitHub地址:

https://github.com/greenrobot/EventBus

Android Studio Gradle配置:

compile 'org.greenrobot:eventbus:3.1.1'

Eclipse

卸载Eclipse,并安装Android Studio

最基础的使用方法

public class EventBusActivity extends AppCompatActivity {

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void canBeAnyNameHereHaHaHa(MyTestEvent event) {
        // 收到消息后打印
        Toast.makeText(this, event.getMsg(), Toast.LENGTH_SHORT).show();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_event_bus);
        // 第一步:注册订阅者身份
        EventBus.getDefault().register(this);
        // 第二步:发送消息给自己(双重身份:订阅者&发布者)
        EventBus.getDefault().post(new MyTestEvent("Hello EventBus!"));
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销之前先判断是否已注册
        if (EventBus.getDefault().isRegistered(this))
            // 关闭页面的时候,一定要注销,不然会造成内存泄漏
            EventBus.getDefault().unregister(this);
    }

}

我们结合上面提到的修改头像为例,来看看用EventBus应该怎么做

  1. 定义通知实体类(也可不定义,使用String,Integer等任意类型都可以)
/**
 * Created by ZengCS on 2018/8/17.
 * E-mail:zcs417327734@gmail.com
 */
public class UpdateAvatarEvent {
    private String url;// 头像云端地址

    public UpdateAvatarEvent(String url) {
        this.url = url;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }
}
  1. 在需要订阅通知的地方进行注册和注销,并写一个订阅方法(方法需要写注解 @Subscribe(threadMode = ThreadMode.XXX))
    每个订阅者可以订阅多种通知类型,没有限制。

官方推荐注册&注销方式:

@Override
public void onStart() {
    super.onStart();
    EventBus.getDefault().register(this);
}

@Override
public void onStop() {
    super.onStop();
    EventBus.getDefault().unregister(this);
}

但是官方推荐的方式经常出现灵异事件,所以大牛们还是推荐这样写:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    
    // 注册EventBus
    EventBus.getDefault().register(this);
}

@Override
protected void onDestroy() {
    super.onDestroy();
    // 注销之前先判断是否已注册
    if (EventBus.getDefault().isRegistered(this))
        EventBus.getDefault().unregister(this);
}

下面我们看看在主页和个人中心如何订阅修改头像的通知:

/**
 * Created by ZengCS on 2018/8/17.
 * E-mail:zcs417327734@gmail.com
 */
public class IndexActivity extends AppCompatActivity {
    private ImageView mImageView;

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onUpdateAvatarEvent(UpdateAvatarEvent event) {
        if (event == null) return;
        String url = event.getUrl();
        if (!TextUtils.isEmpty(url))
            Glide.with(this)
                    .load(url)
                    .into(mImageView);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_index);
        mImageView = findViewById(R.id.id_iv_avatar);
        // 注册EventBus
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销之前先判断是否已注册
        if (EventBus.getDefault().isRegistered(this))
            EventBus.getDefault().unregister(this);
    }
}
/**
 * Created by ZengCS on 2018/8/17.
 * E-mail:zcs417327734@gmail.com
 */
public class UserCenterActivity extends AppCompatActivity {
    private ImageView mImageView;

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onUpdateAvatarEvent(UpdateAvatarEvent event) {
        if (event == null) return;
        String url = event.getUrl();
        if (!TextUtils.isEmpty(url))
            Glide.with(this)
                    .load(url)
                    .into(mImageView);
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_user_center);
        mImageView = findViewById(R.id.id_iv_avatar);
        // 注册EventBus
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // 注销之前先判断是否已注册
        if (EventBus.getDefault().isRegistered(this))
            EventBus.getDefault().unregister(this);
    }
}
  1. 在任意地方调用EventBus.getDefault().post(object)
/**
 * Created by ZengCS on 2018/8/17.
 * E-mail:zcs417327734@gmail.com
 */
public class EditAvatarActivity extends AppCompatActivity {
    // 非相关代码已删除,这里假装头像上传成功了,并把云端头像地址返回给我们了
    public void onUploadSuccess(String avatarUrl) {
        // 发送更新头像通知
        EventBus.getDefault().post(new UpdateAvatarEvent(avatarUrl));
    }
}
如此方便快捷的使用方式,确实是做APP层消息通知的不二选择。不过在注册过EventBus的类中,千万别忘了在结束的时候注销。

这么好用的东西还是需要去了解一下其内部实现的。而且EventBus内部实现其实并不复杂。

3.EventBus源码解析

获取EventBus实例对象

/** Convenience singleton for apps using a process-wide EventBus instance. */
public static EventBus getDefault() {
    if (defaultInstance == null) {
        synchronized (EventBus.class) {
            if (defaultInstance == null) {
                defaultInstance = new EventBus();
            }
        }
    }
    return defaultInstance;
}

可以看到,这是一个典型的DCL单例模式。单例模式获取一个EventBus对象,保证所有的EventBus对象是同一个。

下面我们来看看构造函数:

/**
 * Creates a new EventBus instance; each instance is a separate scope in which events are delivered. To use a
 * central bus, consider {@link #getDefault()}.
 */
public EventBus() {
    this(DEFAULT_BUILDER);
}

EventBus(EventBusBuilder builder) {
    logger = builder.getLogger();
    subscriptionsByEventType = new HashMap<>();
    typesBySubscriber = new HashMap<>();
    stickyEvents = new ConcurrentHashMap<>();
    mainThreadSupport = builder.getMainThreadSupport();
    mainThreadPoster = mainThreadSupport != null ? mainThreadSupport.createPoster(this) : null;
    backgroundPoster = new BackgroundPoster(this);
    asyncPoster = new AsyncPoster(this);
    indexCount = builder.subscriberInfoIndexes != null ? builder.subscriberInfoIndexes.size() : 0;
    subscriberMethodFinder = new SubscriberMethodFinder(builder.subscriberInfoIndexes,
            builder.strictMethodVerification, builder.ignoreGeneratedIndex);
    logSubscriberExceptions = builder.logSubscriberExceptions;
    logNoSubscriberMessages = builder.logNoSubscriberMessages;
    sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
    sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
    throwSubscriberException = builder.throwSubscriberException;
    eventInheritance = builder.eventInheritance;
    executorService = builder.executorService;
}

两个构造方法,一个是无参的public的方法,另外一个是内部方法,带参数的。

内部构造方法中创建了几个容器,用来装各种订阅者信息的,很关键的几个成员我们需要注意,mainThreadPoster 、backgroundPoster 、asyncPoster ,这个几个是后来post消息的时候,用的到的,就是利用他们将消息在不同的线程发送出去,达到在不同线程中执行的效果。

这边DEFAULT_BUILDER声明如下:
private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();

我们看看EventBusBuilder在做什么:

/*
 * Copyright (C) 2012-2016 Markus Junginger, greenrobot (http://greenrobot.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.greenrobot.eventbus;

import android.os.Looper;

import org.greenrobot.eventbus.meta.SubscriberInfoIndex;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Creates EventBus instances with custom parameters and also allows to install a custom default EventBus instance.
 * Create a new builder using {@link EventBus#builder()}.
 */
public class EventBusBuilder {
    private final static ExecutorService DEFAULT_EXECUTOR_SERVICE = Executors.newCachedThreadPool();

    boolean logSubscriberExceptions = true;
    boolean logNoSubscriberMessages = true;
    boolean sendSubscriberExceptionEvent = true;
    boolean sendNoSubscriberEvent = true;
    boolean throwSubscriberException;
    boolean eventInheritance = true;
    boolean ignoreGeneratedIndex;
    boolean strictMethodVerification;
    ExecutorService executorService = DEFAULT_EXECUTOR_SERVICE;
    List<Class<?>> skipMethodVerificationForClasses;
    List<SubscriberInfoIndex> subscriberInfoIndexes;
    Logger logger;
    MainThreadSupport mainThreadSupport;

    EventBusBuilder() {
    }

    /** Default: true */
    public EventBusBuilder logSubscriberExceptions(boolean logSubscriberExceptions) {
        this.logSubscriberExceptions = logSubscriberExceptions;
        return this;
    }

    /** Default: true */
    public EventBusBuilder logNoSubscriberMessages(boolean logNoSubscriberMessages) {
        this.logNoSubscriberMessages = logNoSubscriberMessages;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendSubscriberExceptionEvent(boolean sendSubscriberExceptionEvent) {
        this.sendSubscriberExceptionEvent = sendSubscriberExceptionEvent;
        return this;
    }

    /** Default: true */
    public EventBusBuilder sendNoSubscriberEvent(boolean sendNoSubscriberEvent) {
        this.sendNoSubscriberEvent = sendNoSubscriberEvent;
        return this;
    }

    /**
     * Fails if an subscriber throws an exception (default: false).
     * <p/>
     * Tip: Use this with BuildConfig.DEBUG to let the app crash in DEBUG mode (only). This way, you won't miss
     * exceptions during development.
     */
    public EventBusBuilder throwSubscriberException(boolean throwSubscriberException) {
        this.throwSubscriberException = throwSubscriberException;
        return this;
    }

    /**
     * By default, EventBus considers the event class hierarchy (subscribers to super classes will be notified).
     * Switching this feature off will improve posting of events. For simple event classes extending Object directly,
     * we measured a speed up of 20% for event posting. For more complex event hierarchies, the speed up should be
     * >20%.
     * <p/>
     * However, keep in mind that event posting usually consumes just a small proportion of CPU time inside an app,
     * unless it is posting at high rates, e.g. hundreds/thousands of events per second.
     */
    public EventBusBuilder eventInheritance(boolean eventInheritance) {
        this.eventInheritance = eventInheritance;
        return this;
    }


    /**
     * Provide a custom thread pool to EventBus used for async and background event delivery. This is an advanced
     * setting to that can break things: ensure the given ExecutorService won't get stuck to avoid undefined behavior.
     */
    public EventBusBuilder executorService(ExecutorService executorService) {
        this.executorService = executorService;
        return this;
    }

    /**
     * Method name verification is done for methods starting with onEvent to avoid typos; using this method you can
     * exclude subscriber classes from this check. Also disables checks for method modifiers (public, not static nor
     * abstract).
     */
    public EventBusBuilder skipMethodVerificationFor(Class<?> clazz) {
        if (skipMethodVerificationForClasses == null) {
            skipMethodVerificationForClasses = new ArrayList<>();
        }
        skipMethodVerificationForClasses.add(clazz);
        return this;
    }

    /** Forces the use of reflection even if there's a generated index (default: false). */
    public EventBusBuilder ignoreGeneratedIndex(boolean ignoreGeneratedIndex) {
        this.ignoreGeneratedIndex = ignoreGeneratedIndex;
        return this;
    }

    /** Enables strict method verification (default: false). */
    public EventBusBuilder strictMethodVerification(boolean strictMethodVerification) {
        this.strictMethodVerification = strictMethodVerification;
        return this;
    }

    /** Adds an index generated by EventBus' annotation preprocessor. */
    public EventBusBuilder addIndex(SubscriberInfoIndex index) {
        if (subscriberInfoIndexes == null) {
            subscriberInfoIndexes = new ArrayList<>();
        }
        subscriberInfoIndexes.add(index);
        return this;
    }

    /**
     * Set a specific log handler for all EventBus logging.
     * <p/>
     * By default all logging is via {@link android.util.Log} but if you want to use EventBus
     * outside the Android environment then you will need to provide another log target.
     */
    public EventBusBuilder logger(Logger logger) {
        this.logger = logger;
        return this;
    }

    Logger getLogger() {
        if (logger != null) {
            return logger;
        } else {
            // also check main looper to see if we have "good" Android classes (not Stubs etc.)
            return Logger.AndroidLogger.isAndroidLogAvailable() && getAndroidMainLooperOrNull() != null
                    ? new Logger.AndroidLogger("EventBus") :
                    new Logger.SystemOutLogger();
        }
    }


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

    /**
     * Installs the default EventBus returned by {@link EventBus#getDefault()} using this builders' values. Must be
     * done only once before the first usage of the default EventBus.
     *
     * @throws EventBusException if there's already a default EventBus instance in place
     */
    public EventBus installDefaultEventBus() {
        synchronized (EventBus.class) {
            if (EventBus.defaultInstance != null) {
                throw new EventBusException("Default instance already exists." +
                        " It may be only set once before it's used the first time to ensure consistent behavior.");
            }
            EventBus.defaultInstance = build();
            return EventBus.defaultInstance;
        }
    }

    /** Builds an EventBus based on the current configuration. */
    public EventBus build() {
        return new EventBus(this);
    }

}

里面是一些EventBus的基础配置,打印异常信息,发送未被订阅的消息等。
特别需要注意的是 ignoreGeneratedIndex 这个变量是用来确定EventBus用什么方式来获取订阅方法。
true代表的是不优先使用索引,而用反射的方式,false代表的是优先使用索引。默认是false。
这边有个addIndex方法是用来添加订阅方法索引的,这是3.0版本的新特性,也是高级用法。
在EventBus带参数的构造函数里面初始化了 SubscriberMethodFinder 这个类,三个参数分别为索引列表,
是否严格方法定义,是否无视索引。

SubscriberMethodFinder(List<SubscriberInfoIndex> subscriberInfoIndexes, boolean strictMethodVerification,
                       boolean ignoreGeneratedIndex) {
    this.subscriberInfoIndexes = subscriberInfoIndexes;
    this.strictMethodVerification = strictMethodVerification;
    this.ignoreGeneratedIndex = ignoreGeneratedIndex;
}

getDefault总结:获取一个EventBus的单例对象,用于任何EventBus相关的操作,比如:注册,注销,发送等。

注册

/**
 * Registers the given subscriber to receive events. Subscribers must call {@link #unregister(Object)} once they
 * are no longer interested in receiving events.
 * <p/>
 * Subscribers have event handling methods that must be annotated by {@link Subscribe}.
 * The {@link Subscribe} annotation also allows configuration like {@link
 * ThreadMode} and priority.
 */
public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            subscribe(subscriber, subscriberMethod);
        }
    }
}

首先是通过一个findSubscriberMethods方法找到了一个订阅者中的所有订阅方法,返回一个 List,然后遍历所有的订阅方法,做订阅操作。

进入到findSubscriberMethods看看如何实现的。

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }

    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 {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

我们来看看METHOD_CACHE是怎么实现的
private static final Map<Class<?>, List<SubscriberMethod>> METHOD_CACHE = new ConcurrentHashMap<>();
这里使用了ConcurrentHashMap来对所有的方法进行缓存,为什么要使用ConcurrentHashMap来做方法的缓存呢?无疑是看中了ConcurrentHashMap线程安全且效率高的特性,更多关于ConcurrentHashMap的特性,请自行查阅相关资料。

回到findSubscriberMethods中,首先是通过类来当key,查找在当前缓存中是否存在这个类的订阅方法列表。有就直接return 缓存中的列表。

没有就进入下一步,这边有两种方式来获取类中的订阅方法通过ignoreGeneratedIndex来确定获取订阅方法的方式为true时会使用反射方法获取订阅者的事件处理函数,为false时会优先使用subscriber Index(订阅方法索引)生成的SubscriberInfo来获取订阅者的事件处理函数(减少反射耗时),默认false。

使用订阅方法索引需自己构建一个EventBus对象,本文暂时不讲解索引方式。

下面我们进入findUsingReflection看看是如何用反射获取到订阅方法的

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
    FindState findState = prepareFindState();
    findState.initForSubscriber(subscriberClass);
    while (findState.clazz != null) {
        findUsingReflectionInSingleClass(findState);
        findState.moveToSuperclass();
    }
    return getMethodsAndRelease(findState);
}

private void findUsingReflectionInSingleClass(FindState findState) {
    Method[] methods;
    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();
        if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
            Class<?>[] parameterTypes = method.getParameterTypes();
            if (parameterTypes.length == 1) {
                Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                if (subscribeAnnotation != null) {
                    Class<?> eventType = parameterTypes[0];
                    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)) {
                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)) {
            String methodName = method.getDeclaringClass().getName() + "." + method.getName();
            throw new EventBusException(methodName +
                    " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
        }
    }
}

解读:

  1. 获取订阅类声明的所有方法; 然后对获取到的方法全部遍历一遍
  2. 获取方法的修饰符:即方法前面的public、private等关键字。
  3. 如果该类方法使用了@Subscribe标注、方法中只有一个参数、且方法修饰符为public。findState.checkAdd(method, eventType) 如果之前没有存在过则返回true
  4. 判断@Subscribe标注中的threadMode对应的值,默认模式ThreadMode.POSTING
  5. 创建一个SubscriberMethod(普通的JavaBean)对象,该对象很简单就是保存有方法、方法参数类型、线程模式、订阅的优先级、sticky标志位。并将该对象添加到FindSate的List集合中。

订阅

// Must be called in synchronized block
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);

    if (subscriberMethod.sticky) {
        if (eventInheritance) {
            // Existing sticky events of all subclasses of eventType have to be considered.
            // Note: Iterating over all events may be inefficient with lots of sticky events,
            // thus data structure should be changed to allow a more efficient lookup
            // (e.g. an additional map storing sub classes of super classes: Class -> List<Class>).
            Set<Map.Entry<Class<?>, Object>> entries = stickyEvents.entrySet();
            for (Map.Entry<Class<?>, Object> entry : entries) {
                Class<?> candidateEventType = entry.getKey();
                if (eventType.isAssignableFrom(candidateEventType)) {
                    Object stickyEvent = entry.getValue();
                    checkPostStickyEventToSubscription(newSubscription, stickyEvent);
                }
            }
        } else {
            Object stickyEvent = stickyEvents.get(eventType);
            checkPostStickyEventToSubscription(newSubscription, stickyEvent);
        }
    }
}

解读:

  1. 获取方法参数类型;注意:使用@Subscribe标注的方法有且仅有一个参数
  2. 利用订阅者对象及其事件处理方法构建一个Subscription对象,该对象存储有Object、SubscriberMethod对象
  3. 从subscriptionsByEventType集合中获取当前事件对应的Subscription对象集合; 如果得到的集合为空则创建一个这样的集合,并将刚创建的Subscription对象添加进subscriptionsByEventType集合中;如果得到的集合不为空且刚创建的Subscription对象已经存在该集合中则抛出异常,即同一个对象不能注册两次!
  4. 将第二步创建的Subscription对象按照优先级存入Subscription对象集合中,该集合中的元素都是按照优先级从高到低存放.
  5. 以subscriber对象为键,从typesBySubscriber获取该对象对应的接收事件类型集合,没有则创建一个这样的集合,然后当前事件类型添加到该集合中,最后将整个集合添加进typesBySubscriber集合中;有则直接添加到接收事件类型集合中;
  6. sticky 该值默认为false,除非在注册事件方法时使用了如下的标注@Subscribe(sticky = true);那么就会执行到这里。stickyEvent也是EventBus3.0的一大特点,该类事件一旦发送给EventBus,那么EventBus就会将它存入Map

发布订阅事件

/** Posts the given event to the event bus. */
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;
        }
    }
}
  1. 获取当前线程对应的一个PostingThreadState()对象
  2. 向PostingThreadState的事件队列中添加一个事件
  3. 从PostingThreadState的事件队列——eventQueue中移出一个事件,并调用postSingleEvent方法进行派送

下车

/** Unregisters the given subscriber from all event classes. */
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());
    }
}
  1. 从typesBySubscriber获取该对象接收的事件类型集合;
  2. 对得到的接收事件类型集合中的每个事件类型调用unsubscribeByEventType进行处理;跟着我们就分析该方法
  3. 该对象从typesBySubscriber集合中移除;
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
private void unsubscribeByEventType(Object subscriber, Class<?> eventType) {
    List<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
    if (subscriptions != null) {
        int size = subscriptions.size();
        for (int i = 0; i < size; i++) {
            Subscription subscription = subscriptions.get(i);
            if (subscription.subscriber == subscriber) {
                subscription.active = false;
                subscriptions.remove(i);
                i--;
                size--;
            }
        }
    }
}
  1. 从subscriptionsByEventType集合中获取该事件类型对应的Subscription集合
  2. 如果集合中的元素——Subscription的subscriber域等于目标subscriber,则将该Subscription从subscriptionsByEventType集合中移除出去;

没有精确去分析所有的源码。这边大概做一下总结,整个流程大概如下。

  1. 注册EventBus,EventBus会获取当前类中的订阅方法,包括方法的参数,类型,优先级等信息。在获取的方法中,会先遍历缓存。如果在缓存中没有才去调用获取方法。
  2. 获取订阅方法有两种方式,一种是反射,反射获取全部方法,找到加了@Subscribe注解并有一个参数的方法,另外一种是通过订阅方法索引来得到订阅方法。反射的效率较低,所以优先使用用订阅方法索引来获取,当索引列表找不到这个方法。它就会走反射的途径。注册过后这个订阅方法就被存起来了。等待接受消息。
  3. 在3.0以前是没有注解方法的那时候都是通过反射来获取和执行方法。而且方法必须以onEvent开头,分别为onEventMainThead,onEventBackground,onEvent,onEventAsync,3.0以后就全部替换为注解的形式,方法名可以是任意(只要能通过编译的都可以)
  4. postEvent,会把这个Event加入消息队列,然后通过Event的类型信息来得到它的订阅方法,然后根据相应的订阅方式反射调用订阅方法。没有选择的threadMode的post一般都会在UI线程执行。如果当前post不是UI线程,这边会用Handle的机制来让接受Event的方法运行在UI线程。
  5. 解除注册,将该对象从对象缓存列表中移除,获取当前对象的订阅列表,然后将其从订阅列表移除
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,569评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,499评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,271评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,087评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,474评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,670评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,911评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,636评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,397评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,607评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,093评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,418评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,074评论 3 237
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,092评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,865评论 0 196
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,726评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,627评论 2 270

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,569评论 25 707
  • 作为一个普通公众,对广告通常是一种厌恶的态度。这不能怪消费者怎么不爱广告,而应从我们广告人来反省,为什么不能做出让...
    韩德胜阅读 1,116评论 2 7
  • ①【用户需求】 一般情况下,很多公司都是盯着,行业里的竞争对手,有可能是靠前面的第一二名同行,出新产品啊或者什么,...
    努力学习的清梅阅读 146评论 0 2