EventBus源码赏析六 —— 知识周边

765 阅读8分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第25天,点击查看活动详情

环境检测

EventBus是一种用于Android的发布/订阅事件总线。那么它是如何判断当前环境就是Android呢。

public void register(Object subscriber) {
    if (AndroidDependenciesDetector.isAndroidSDKAvailable() && !AndroidDependenciesDetector.areAndroidComponentsAvailable()) {
        throw new RuntimeException("xxxxxx");
    }
}

可以看到是根据isAndroidSDKAvailable()方法和areAndroidComponentsAvailable()方法检测的

isAndroidSDKAvailable

该方法是判断当前是否运行在Android环境中

public static boolean isAndroidSDKAvailable() {
    try {
        Class<?> looperClass = Class.forName("android.os.Looper");
        Method getMainLooper = looperClass.getDeclaredMethod("getMainLooper");
        Object mainLooper = getMainLooper.invoke(null);
        return mainLooper != null;
    }
    catch (Exception ignored) {//节省篇幅,修改了异常处理}
    return false;
}

我们知道getMainLooper()可以获取Android主线程中的Looper,如果获取不到,则说明环境异常。

areAndroidComponentsAvailable

EventBus核心类和EventBus-Android是两个不同的Module,EventBus-Android对EventBus模块不可见,但是它需要EventBus-Android提供的一些组件。它是如何获取到的呢。

public static boolean areAndroidComponentsAvailable() {
    try {
        Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME);
        return true;
    }
    catch (ClassNotFoundException ex) { return false;}
}

public static AndroidComponents instantiateAndroidComponents() {
    try {
        Class<?> impl = Class.forName(ANDROID_COMPONENTS_IMPLEMENTATION_CLASS_NAME);
        return (AndroidComponents) impl.getConstructor().newInstance();
    }
    catch (Throwable ex) { return null;}
}

可以看到,这里使用反射查找AndroidComponentsImpl类,如果查找到了,说明EventBus-Android在运行环境中,然后使用newInstance()获取其实例对象。

对象池

为了减少对象频繁创建/销毁的开销,EventBus多次使用了对象池。

线程池

EventBus默认使用的线程池由newCachedThreadPool()创建的可缓存线程池。这种情况下,工作线程创建的数量上限是Interger.MAX_VALUE,所以这种情况下我们应该避免在同一时间进行大量的异步订阅(ASYNC线程模型),控制并发线程的数量。

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

FindState池

EventBus为FindState提供了一个大小为4的缓存池

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

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

从池中取回FindState,如果没有就新建。

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

PendingPost池

PendingPost存储了当前订阅信息以及下一个订阅信息的引用,在发送事件的时候,该对象创建非常频繁,为了减少内存抖动,EventBus也提供了PendingPost池。

private final static List<PendingPost> pendingPostPool = new ArrayList<PendingPost>();

pendingPostPool池的最大长度为10000。

static void releasePendingPost(PendingPost pendingPost) {
    pendingPost.event = null;
    pendingPost.subscription = null;
    pendingPost.next = null;
    synchronized (pendingPostPool) {
        if (pendingPostPool.size() < 10000) {
            pendingPostPool.add(pendingPost);
        }
    }
}

回收PendingPost的时候,首先要对其进行释放,然后放回池中,为了防止池的大小野蛮生长,这里限制了最大为10000。

复用

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

复用之前首先检查复用池中是否有可用,如果有则返回复用,否则返回一个新的。

ThreadLocal

ThreadLocal叫做线程变量,它里面的变量属于当前线程独有的,对于其他线程隔离,它解决了多线程并发访问数据混乱的问题。

EventBus中使用PostingThreadState保存了当前发布线程的事件队列,发布状态,发布事件等信息。

final static class PostingThreadState {
    final List<Object> eventQueue = new ArrayList<>();
    boolean isPosting;
    boolean isMainThread;
    Subscription subscription;
    Object event;
    boolean canceled;
}

这些信息是当前线程独有的,不应该对其他线程可见,EventBus使用ThreadLocal解决了此问题。

private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
    @Override
    protected PostingThreadState initialValue() {
        return new PostingThreadState();
    }
};

每个线程第一次访问的时候都会初始化一份PostingThreadState,后续同一线程使用的都是同一个变量。

位运算

在通过反射获取订阅方法的时候,需要判断方法修饰符是否正确,这里使用了位运算,提高了运算效率。

private void findUsingReflectionInSingleClass(FindState findState) {
	if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
	}
}
//Modifier.PUBLIC=0x00000001
//MODIFIERS_IGNORE = ABSTRACT | STATIC | BRIDGE | SYNTHETIC = 0x00000400 | 0x00000008 | 0x40 | 0x1000

这里访问修饰(PUBLIC,ABSTRACT等)定义的值都是有特点的,他们的是2的整数次幂,也就是他们二进制表示上只有一位为1,其余位都是0。 当他们做或运算(|)的时候,该位置的值一直都是1,即或运算的结果(modifiers)会存储这些修饰符的信息,那么我们怎么判断modifiers是否包含某一个修饰符呢,就用到了与运算符(&),因为访问修饰只有一位为1,其余位都是0,那么他和modifiers与运算之后,如果modifiers包含当前修饰符,那么对应为也是1,即结果不为0,否则结果等于0。

获取泛型的真实类型

在定义订阅方法的时候,传入的参数可能是泛型

@Subscribe
public void collectEvent(T event) {
    events.add(event);
}

这种情况对于反射查找来说比较简单,都是Object,对于使用注解处理器是这样获取的

private TypeMirror getParamTypeMirror(VariableElement param, Messager messager) {
    TypeMirror typeMirror = param.asType();
    if (typeMirror instanceof TypeVariable) {
        TypeMirror upperBound = ((TypeVariable) typeMirror).getUpperBound();
        if (upperBound instanceof DeclaredType) {
            typeMirror = upperBound;
        }
    }
    return typeMirror;
}

如果此参数的类型typeMirror是泛型类型的话,通过getUpperBound()方法获取其上边界,如果上边界是类或者接口,以此作为参数类型。如果没有明确声明上边界,则为Object。

队列

EventBus自己实现了一个简单的队列

定义

final class PendingPostQueue {
    private PendingPost head;
    private PendingPost tail;
}

他基于链表实现,是一个先进先出的队列(FIFO),内部维护了头尾指针两个变量。头部head获取/删除元素,尾部tail添加元素,这样保证了元素的插入和删除时间复杂度都是O(1)。

入队

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

插入元素的时候,采用的是尾部插入,首先插入队列的元素不能为null,然后如果队列尾部不为null,则让旧尾部指向插入的元素,然后让插入的元素成为新的尾部。如果尾部为null,并且头部也为null,说明此时队列为空,就让入队的元素成为队列的首尾,否则说明队列异常。插入完成后还需要调用notifyAll()唤醒当前对象上的等待线程。

出队

synchronized PendingPost poll() {
    PendingPost pendingPost = head;
    if (head != null) {
        head = head.next;
        if (head == null) {
            tail = null;
        }
    }
    return pendingPost;
}

出队的时候返回头部元素,如果头节点元素不为空,就让头节点下一个元素作为新的头节点,如果新的头节点为null,说明队空了,还需要维护尾部元素,让它也指向null。

PendingPostQueue还重载了poll()方法

synchronized PendingPost poll(int maxMillisToWait) throws InterruptedException {
    if (head == null) {
        wait(maxMillisToWait);
    }
    return poll();
}

外部调用的时候maxMillisToWait传入的是1000,wait(1000)防止队列中刚没有任务了就立马停止线程,最大化使用线程资源,防止频繁创建/销毁线程。

生产者-消费者

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。Java实现生产者消费者的方式比较多,EventBus采用自定义的队列PendingPostQueue和synchronized/wait()/notifyAll()实现。

final class BackgroundPoster implements Runnable, Poster {
    private final PendingPostQueue queue;
	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) {
                            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;
        }
    }
}
  • 生产者每生产一个PendingPost都放入队列并且调用notifyAll()唤醒线程。
  • 消费者不断从队列取出元素,如果遇到队列为空,就wait(1000)是线程阻塞。

设计模式

观察者模式

定义了对象之间的一对多依赖, 当一个对象改变状态时,它的所有依赖者都会收到通知并自动更新。

观察者模式是EventBus的核心,其中订阅者是观察者,事件作为被观察者。

在传统的观察者模式中,被观察者维护观察者列表,并且在自身改变的时候通知观察者。而在EventBus中,观察者列表维护和事件发送都由EventBus处理。

单例模式

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

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

EventBus使用了双重锁检查获取defaultInstance,与传统单例模式不一样的是,EventBus提供单例获取实例只是为了保证defaultInstance唯一,而不是确保整个类都只有一个实例。他还提供了其他方式获取EventBus实例。

建造者模式

将一个复杂的构建与其表示相分离,使得同样的构建过程可以创建不同的表示。

//EventBus
public static EventBusBuilder builder() {
    return new EventBusBuilder();
}
//EventBusBuilder
public EventBusBuilder eventInheritance(boolean eventInheritance) {
    this.eventInheritance = eventInheritance;
    return this;
}
public EventBusBuilder executorService(ExecutorService executorService) {
    this.executorService = executorService;
    return this;
}
//...省略其他方法
//构造EventBus实例
public EventBus build() {
    return new EventBus(this);
}

使用建造者模式我们可以自行调整EventBus的配置,比如强制使用反射(ignoreGeneratedIndex)

适配器模式

将一个类的接口转换成客户希望的另外一个接口。适配器模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作。

在通过注解处理器查找订阅方法的时候SubscriberMethodFinder#findUsingInfo(),需要返回的是SubscriberMethod[]

SubscriberMethod[] array = findState.subscriberInfo.getSubscriberMethods();

但是在注解处理器处理的过程中找到的是SubscriberMethodInfo对象。如何让两个不同的类兼容工作呢,这里使用了适配器模式。

public class SimpleSubscriberInfo extends AbstractSubscriberInfo {

    private final SubscriberMethodInfo[] methodInfos;

    public SimpleSubscriberInfo(Class subscriberClass, boolean shouldCheckSuperclass, SubscriberMethodInfo[] methodInfos) {
        super(subscriberClass, null, shouldCheckSuperclass);
        this.methodInfos = methodInfos;
    }

    @Override
    public synchronized SubscriberMethod[] getSubscriberMethods() {
        int length = methodInfos.length;
        SubscriberMethod[] methods = new SubscriberMethod[length];
        for (int i = 0; i < length; i++) {
            SubscriberMethodInfo info = methodInfos[i];
            methods[i] = createSubscriberMethod(info.methodName, info.eventType, info.threadMode,
                    info.priority, info.sticky);
        }
        return methods;
    }
}

SimpleSubscriberInfo类继承AbstractSubscriberInfo,重写getSubscriberMethods()方法,通过反射创建SubscriberMethod对象。