EventBus 设计感悟

说在前面的话:

EventBus相信大家已经不陌生了,主要功能是替代Intent、Handler、BroadCast在各组件、线程之间传递消息。他的最牛逼优点是开销小,代码简洁,解耦代码。,因此怎么使用就不在此叙述,此文主要是通过全局设计去理解设计框架,来咱们开始吧~


EventBus 设计感悟_第1张图片

上图是官方给的,从这个图中咱们就能知道是基于观察者模式架构,所以具备3个要素
Event:事件
Subscriber:事件订阅者,接收特定的事件。
Publisher:事件发布者,用于通知Subscriber有事件发生。

「我们从创建来看:」

static volatile EventBus defaultInstance;

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

用双判单例模式生成实例:

这里用两次判断,是解决多线程并发问题,但由于执行defaultInstance = new EventBus();并非是一个原子性操作,关键执行以下步骤
1.给EventBus实例分配内存空间
2.初始化EventBus的构造器
3.将defaultInstance指向分配的内存空间(此时非null)
但是,由于Java编译器允许处理器乱序执行(out-of-order),以及JDK1.5之前JMM(Java Memory Medel)中Cache、寄存器到主内存回写顺序的规定,上面的第二点和第三点的顺序是无法保证的,也就是说,执行顺序可能是1-2-3也可能是1-3-2,如果是后者,并且在3执行完毕、2未执行之前,被切换到线程二上,这时候defaultInstance因为已经在线程一内执行过了第三点,defaultInstance已经是非空了,所以线程二直接拿走defaultInstance,然后使用,然后报错,在JDK1.5之后,官方已经注意到这种问题,因此调整了JMM、具体化了volatile关键字,还是建议使用内部类方式,利用jvm机制保证单例.

这里有说明volatile作用 http://www.cnblogs.com/zhaoliu/p/5389972.html

build建造者模式

这里单例方法上注释说是为方便app获取这个实例,因为在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) {
        subscriptionsByEventType = new HashMap, CopyOnWriteArrayList>();
        typesBySubscriber = new HashMap>>();
        stickyEvents = new ConcurrentHashMap, Object>();
        mainThreadPoster = new HandlerPoster(this, Looper.getMainLooper(), 10);
        backgroundPoster = new BackgroundPoster(this);
        asyncPoster = new AsyncPoster(this);
        subscriberMethodFinder = new SubscriberMethodFinder(builder.skipMethodVerificationForClasses);
        logSubscriberExceptions = builder.logSubscriberExceptions;
        logNoSubscriberMessages = builder.logNoSubscriberMessages;
        sendSubscriberExceptionEvent = builder.sendSubscriberExceptionEvent;
        sendNoSubscriberEvent = builder.sendNoSubscriberEvent;
        throwSubscriberException = builder.throwSubscriberException;
        eventInheritance = builder.eventInheritance;
        executorService = builder.executorService;
    }

但是不是所有元素都通过EventBusBuilder 来生成,在builder中是一些可自定义配置,公共必须要有的成员放在构造方法中生成

EventBus类

1.事件订阅者:
我们知道在观察者模式中,要想成为订阅者,需要实现接口成为订阅者,当发布者有信息发布是会遍历所有订阅者并回调方法通知订阅者,而EventBus则利用固定4种方法名称 + 参数类型来作为他的不同功能的消息回调方法,这样做的好处是订阅者不用另外实现接口,更简洁。缺点是当接受消息过多时,造成方法混乱和数量多,不利于维护,所以在3.0版本改用注解来实现此功能,这个会在另外一篇文章中详解.
我们在开发中要想使我们具体某一个类接受消息是通过注册 ( EventBus.getDefault().register(this) )成为一个事件订阅者:我们通过具体代码来看看怎么实现的:

private synchronized void register(Object subscriber, boolean sticky, int priority) {
    List subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
    for (SubscriberMethod subscriberMethod : subscriberMethods) {
        subscribe(subscriber, subscriberMethod, sticky, priority);
    }
}

注册最后都会调用此方法:首先是subscriberMethodFinder.findSubscriberMethods(subscriber.getClass()),subscriberMethodFinder这个类的核心职责就是把订阅者中的约定方法全都找出来,存到map中

private static final Map> methodCache = new HashMap>();
List findSubscriberMethods(Class subscriberClass) {
    String key = subscriberClass.getName();
    List subscriberMethods;
    synchronized (methodCache) {
        subscriberMethods = methodCache.get(key);
    }
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    subscriberMethods = new ArrayList();
    Class clazz = subscriberClass;
    HashSet eventTypesFound = new HashSet();
    StringBuilder methodKeyBuilder = new StringBuilder();
    while (clazz != null) {
        String name = clazz.getName();
        if (name.startsWith("java.") || name.startsWith("javax.") || name.startsWith("android.")) {
            // Skip system classes, this just degrades performance
            break;
        }

        // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
        Method[] methods = clazz.getDeclaredMethods();
        for (Method method : methods) {
            String methodName = method.getName();
            if (methodName.startsWith(ON_EVENT_METHOD_NAME)) {
                int modifiers = method.getModifiers();
                if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                    Class[] parameterTypes = method.getParameterTypes();
                    if (parameterTypes.length == 1) {
                        String modifierString = methodName.substring(ON_EVENT_METHOD_NAME.length());
                        ThreadMode threadMode;
                        if (modifierString.length() == 0) {
                            threadMode = ThreadMode.PostThread;
                        } else if (modifierString.equals("MainThread")) {
                            threadMode = ThreadMode.MainThread;
                        } else if (modifierString.equals("BackgroundThread")) {
                            threadMode = ThreadMode.BackgroundThread;
                        } else if (modifierString.equals("Async")) {
                            threadMode = ThreadMode.Async;
                        } else {
                            if (skipMethodVerificationForClasses.containsKey(clazz)) {
                                continue;
                            } else {
                                throw new EventBusException("Illegal onEvent method, check for typos: " + method);
                            }
                        }
                        Class eventType = parameterTypes[0];
                        methodKeyBuilder.setLength(0);
                        methodKeyBuilder.append(methodName);
                        methodKeyBuilder.append('>').append(eventType.getName());
                        String methodKey = methodKeyBuilder.toString();
                        if (eventTypesFound.add(methodKey)) {
                            // Only add if not already found in a sub class
                            subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
                        }
                    }
                } else if (!skipMethodVerificationForClasses.containsKey(clazz)) {
                    Log.d(EventBus.TAG, "Skipping method (not public, static or abstract): " + clazz + "."
                            + methodName);
                }
            }
        }
        clazz = clazz.getSuperclass();
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass + " has no public methods called "
                + ON_EVENT_METHOD_NAME);
    } else {
        synchronized (methodCache) {
            methodCache.put(key, subscriberMethods);
        }
        return subscriberMethods;
    }
}

此处缓存key是订阅者对象 value是SubscriberMethod集合


EventBus 设计感悟_第2张图片
Paste_Image.png

解析过程
1.如果缓存methodCache中有此订阅者对象,返回;
2.反射获取所有申明的方法
检查是否是约定方法

if (modifierString.length() == 0) {
    threadMode = ThreadMode.PostThread;
} else if (modifierString.equals("MainThread")) {
    threadMode = ThreadMode.MainThread;
} else if (modifierString.equals("BackgroundThread")) {
    threadMode = ThreadMode.BackgroundThread;
} else if (modifierString.equals("Async")) {
    threadMode = ThreadMode.Async;
}

符合条件的会增加到集合中
subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));

此处有一个判断,断点methodKey = "onEventBackgroundThread>leon.trainingproject.AnyEventType"

if (eventTypesFound.add(methodKey)) {
    // Only add if not already found in a sub class
    subscriberMethods.add(new SubscriberMethod(method, threadMode, eventType));
}

这里eventTypesFound是一个HashSet,作用是利用HashSet不能有重复key值功能,过滤订阅者中父类和子类有方法重写的情况,EventBus只当做一个处理.

此时已经找到约定方法,然后调用

 private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
    Class eventType = subscriberMethod.eventType;
    CopyOnWriteArrayList subscriptions = subscriptionsByEventType.get(eventType);
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);
    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);
        }
    }

    // Starting with EventBus 2.2 we enforced methods to be public (might change with annotations again)
    // subscriberMethod.method.setAccessible(true);

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

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

    if (sticky) {
        Object stickyEvent;
        synchronized (stickyEvents) {
            stickyEvent = stickyEvents.get(eventType);
        }
        if (stickyEvent != null) {
            // If the subscriber is trying to abort the event, it will fail (event is not tracked in posting state)
            // --> Strange corner case, which we don't take care of here.
            postToSubscription(newSubscription, stickyEvent, Looper.getMainLooper() == Looper.myLooper());
        }
    }
}

此方法前半部分实现了注册功能,后半部分是如果有sticky事件,则立即响应事件的功能.

核心就是
private final Map, CopyOnWriteArrayList> subscriptionsByEventType;

集合记录了事件和订阅者,key是订阅的事件Event,value是订阅此事件的订阅者集合

你可能感兴趣的:(EventBus 设计感悟)