EventBus框架原理解析(结合源码)(上)

上一篇文章http://blog.csdn.net/crazy__chen/article/details/47425779

和大家一起模仿EventBus的实现机制,和大家一起写出了一个简易的EventBus。通过这个项目,大家应该对EventBus的实现机理由大致的了解。

建议大家在看这篇文章之前,先读上一篇文章,有助于了解。

本篇文章希望通过对Github上EventBus的源码,向大家讲些其实现细节。

网上有很多讲EventBus架构的文章,我看了以后都觉得不大清晰,希望这篇文章可以帮到你。

EventBus源码地址https://github.com/greenrobot/EventBus

对于整个框架的理解,我们先来看一下文件图

我们希望了解整个框架的每个文件存在的意义,在这里我将详细介绍大部分主要类的用处和设计的思路。

我们都知道整个框架的入口是EventBus这个类,网上很多文章都是以这个类为入口

我却先给大家将一些实体类的构建。

我们知道,EventBus解耦的方式其实是使用了反射,我在调用register()方法的时候,EventBus就扫描调用这个方法的类,将需要反射调用的方法信息记录下来,这些方法就是onEvent(),onEventMainThread(),onEventBackgroundThread()和onEventAsync()。

这些方法被记录以后,如果有人调用post()方法,就会在记录里面查找,然后使用发射去触发这些方法

反射触发方法的方式是调用Method类的invoke()方法,其签名如下:

public Object invoke(Object obj, Object... args)

显然要我们需要1,Method对象,订阅者对象obj,还有参数对象args

也就是说记录里面,对应一个订阅方法,起码要有上面三个参数。

OK,根据面向对象的思想,我们最好将这些参数封装成一个具体的类。接下来我们看一张图

这张图说明了EventBus里面实体类的构造结构,外层的类包裹着里面的类。也就是外面的方框有一个里面方框所代表的类,作为它的属性

我们可以看到PendingPost包含Subscription,Subscription包含SubscriberMethod,SubscriberMethod包含ThreadMode

我现在向大家说明一下。

首先,最里面的是SubscriberMethod类,它的意义就是EventBus扫描到的订阅方法主体

final class SubscriberMethod {
    /**
     * 方法本体
     */
    final Method method;
    /**
     * 订阅类型
     */
    final ThreadMode threadMode;
    /**
     * 参数类型
     */
    final Class<?> eventType;

这个类包含了EventBus扫描到的方法Method,订阅类型ThreadMode,其实是一个枚举类,它代表订阅方法的类型(是在主线程,子线程,还是在发布线程运行等等)

public enum ThreadMode {
    PostThread,
    MainThread,
    BackgroundThread,
    Async
}

还有订阅方法的参数Class<?> eventType

试想一下,要如果要反射调用这个方法,那么我们现在只满足了第一个条件,就是我们获得了方法对象本身

然后是Subscription,它的意义就是一个订阅,订阅包含订阅者和订阅方法

final class Subscription {
    /**
     * 订阅者
     */
    final Object subscriber;
    /**
     * 订阅的方法
     */
    final SubscriberMethod subscriberMethod;
    /**
     * 优先级
     */
    final int priority;
    ......
}

可以见到,有订阅者,订阅方法对象SubscriberMethod,一个优先级,优先级是用于队列排序的,后面会讲到,优先级越高,当post的时候,者订阅就越先被触发。

这时反射的第二个条件,反射要传入的第一个参数就具备了。

再次封装成PendingPost,这个类的意义是在队列中排队的一个具体执行请求,也就是post以后,就会产生这样一个对象

final class PendingPost {
    Object event;
    Subscription subscription;
    PendingPost next;
    .......
}

其中Object event,就是我们post(Object obj)方法调用的时候,参数的obj,这就是我们反射需要的最后一个参数!

这样,我们通过构建这几个层次分明的实体类,我们就具备了触发反射的能力。

通过上面的分析我们也知道,我们在register()的时候,目的是获得Subscription对象

我们在post(Object obj)的时候,目的是将传入的obj和相应的Subscription对象一起封装成PendingPost对象

将其放入队列里面排队,队列会不断取出元素,触发反射!

了解上面思路以后,我们开始从EventBus入手,看看最主要的register和post方法,具体是再怎么做到的。

再次之前,我们要看EventBus里面的几个重要属性,这几个属性很复杂,大家先有个印象

public class EventBus {

    /** Log tag, apps may override it. */
    public static String TAG = "Event";

    static volatile EventBus defaultInstance;

    private static final EventBusBuilder DEFAULT_BUILDER = new EventBusBuilder();
    /**
     * key为参数类型,value为参数类型及其父类型,接口类型列表
     */
    private static final Map<Class<?>, List<Class<?>>> eventTypesCache = new HashMap<Class<?>, List<Class<?>>>();
    /**
     * key为参数类型,value为该参数类型的所有订阅(订阅包括,订阅者,订阅方法,优先级)
     */
    private final Map<Class<?>, CopyOnWriteArrayList<Subscription>> subscriptionsByEventType;
    /**
     * key为订阅者,value为参数列表
     */
    private final Map<Object, List<Class<?>>> typesBySubscriber;
    /**
     * 可以看做缓存,key参数类型,value是参数
     */
    private final Map<Class<?>, Object> stickyEvents;

    .......
}

这么多属性有什么意义?有的是为了功能必须,有的是为了提高性能,例如缓存。再看源码过程中我会看到他们的作用。

但是现在我们必须关注的一个属性是subscriptionsByEventType,这个属性我们在上一篇文章也说过它的重要作用。

subscriptionsByEventType的key是参数类型,例如我们又一个onEvent(Info i)方法,我们知道EventBus是通过参数类型找到订阅方法的,例如post(new Info("msg"));

EventBus负责找到所以订阅了Info类型的订阅方法,例如上面说的onEvent(Info i)

那么这就要求我们将Info.class记录下来,作为key,通过这个key,我们就可以找到所有这些方法了。

所以,subscriptionsByEventType的value是一个ArrayList,里面存储了Subscription。

OK,现在让我们来看register()方法

private synchronized void register(Object subscriber, boolean sticky, int priority) {
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriber.getClass());
        for (SubscriberMethod subscriberMethod : subscriberMethods) {//遍历改订阅者的所有订阅方法
            subscribe(subscriber, subscriberMethod, sticky, priority);
        }
    }

在这个方法里面,调用了subscriberMethodFinder这个类的findSubscriberMethods()方法,根据上面的分析我们也知道,这个方法用于找出订阅者subscriber中的所有订阅方法,并且将它们封装成SubscriberMethod对象返回。

然后调用了subscribe()方法,将SubscriberMethod对象,和订阅者一起,封装成了subscription对象,我们具体看这两个方法。

首先是findSubscriberMethods()

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
        String key = subscriberClass.getName();//订阅者名称
        List<SubscriberMethod> subscriberMethods;//订阅的方法集
        synchronized (methodCache) {
            subscriberMethods = methodCache.get(key);
        }
        if (subscriberMethods != null) {
            return subscriberMethods;
        }
        subscriberMethods = new ArrayList<SubscriberMethod>();
        Class<?> clazz = subscriberClass;
        /**
         * 记录订阅的所有方法,防止父类重复订阅在子类中已经订阅的方法
         */
        HashSet<String> eventTypesFound = new HashSet<String>();
        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) {//只找public方法,而且没有static,final等修饰
                        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);
                    }
                }
            }
            <pre name="code" class="java">           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;
} }


上面的代码有点复杂,其实就是扫描订阅者,找到订阅方法。

注意有这样一个

 /**
         * 记录订阅的所有方法,防止父类重复订阅在子类中已经订阅的方法
         */
        HashSet<String> eventTypesFound = new HashSet<String>();

这个eventTypesFound是记录了整个扫描过程中扫描到的方法签名,为什么要记录呢?

看到这一句

clazz = clazz.getSuperclass();//查找父类

也就是说扫描一个订阅者的时候,如果这个订阅者本身没有订阅方法,但是它的父类有订阅方法,也会被扫描出来

这种优化就是为了实现继承的作用。

所以扫描父类的时候,要避免重复记录子类中已经扫描到的订阅方法。

另外还有一点,就是skipMethodVerificationForClasses,它可以用于过滤不需要扫描的方法,也就是这些订阅不会被记录

这个属性时在创建时传入的对象,我们看SubscriberMethodFinder的构造函数

SubscriberMethodFinder(List<Class<?>> skipMethodVerificationForClassesList) {
        skipMethodVerificationForClasses = new ConcurrentHashMap<Class<?>, Class<?>>();
        if (skipMethodVerificationForClassesList != null) {
            for (Class<?> clazz : skipMethodVerificationForClassesList) {
                skipMethodVerificationForClasses.put(clazz, clazz);
            }
        }
    }

传入一个名单,名单内的订阅者将会被忽略。

最后,在封装过程中,根据方法签名,构造SubscriberMethod,并且将SubscriberMethod添加到队列里面,最后返回这个队列。

这样findSubscriberMethods()就结束了。

我们接着register()方法里面的

for (SubscriberMethod subscriberMethod : subscriberMethods) {//遍历改订阅者的所有订阅方法
            subscribe(subscriber, subscriberMethod, sticky, priority);
        }

这里将方法封装成subscription

// Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod, boolean sticky, int priority) {
        Class<?> eventType = subscriberMethod.eventType;
        CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(eventType);
        Subscription newSubscription = new Subscription(subscriber, subscriberMethod, priority);//封装成<span style="font-family: Arial, Helvetica, sans-serif;">newSubscription</span>
        if (subscriptions == null) {
            subscriptions = new CopyOnWriteArrayList<Subscription>();
            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<Class<?>> subscribedEvents = typesBySubscriber.get(subscriber);//这里是为了删除订阅的需要
        if (subscribedEvents == null) {
            subscribedEvents = new ArrayList<Class<?>>();
            typesBySubscriber.put(subscriber, subscribedEvents);
        }
        subscribedEvents.add(eventType);

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

上面,根据首先封装出一个subscription,然后判断subscriptionsByEventType里面是否由参数类型对应的队列,没有就创建一个

将subscription加入队列。

然后就涉及到一个typesBySibcriber属性,看我注释

/**
     * key为订阅者,value为参数类型列表
     */
    private final Map<Object, List<Class<?>>> typesBySubscriber;

也就是说这个属性可以告诉我们,某个订阅者,订阅了哪些参数类型

通过这个记录,我们可以为某个订阅者解除订阅,看EventBus里面的unregister()方法

/** 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) {
                unubscribeByEventType(subscriber, eventType);//为该类型解除订阅
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }
/** Only updates subscriptionsByEventType, not typesBySubscriber! Caller must update typesBySubscriber. */
    private void unubscribeByEventType(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--;
                }
            }
        }
    }

通过上面的方法,再在subscriptionsByEventType中找到参数类型列表中的subscription队列,再在队列中找到订阅者subscriber,将它们从队列清除

这样就实现了解除订阅。

OK,subscribe()方法的最后,还有一个关于sticky的判断,这个我们后面再讲,也就是subscribe()方法先讲到这,register()方法也讲到这!

下一篇文章,我会继续讲post()方法的实现。

如果对文章内容有任何疑惑,欢迎留言。

转载请注明出处。

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-11-29 09:01:51

EventBus框架原理解析(结合源码)(上)的相关文章

EventBus框架原理解析(结合源码)(下)

上一篇文章EventBus框架原理解析(结合源码)(上),给大家讲述了EventBus中实体类的封装和register()的具体代码. 接下来我们看另外一个重要方法post(),这个方法显然是要根据传入的参数类型,从subscriptionsByEventType取出对应的subscription /** Posts the given event to the event bus. */ public void post(Object event) {//event就是参数 PostingTh

GlusterFS源码解析 —— GlusterFS 源码安装

安装环境: CentOS6.2 glusterfs-3.4.3 GlusterFS 挂载需要 fuse 支持,如果你的内核版本低于 2.6.16 则需要下载fuse的源码包自行编译安装,也可下载 fuse 的rpm包.安装fuse的方法我就不说了,不会源码安装的直接去rpmfind.net上下载rpm即可.高于此版本的内核中已经有了fuse.ko的模块,需要的时候可以执行以下命令进行加载: modprobe -b fuse 1.下载GlusterFS的源码包,目前已经有更新版本 : wget h

Android中图片加载框架Glide解析2----从源码的角度理解Glide的执行流程

转载地址:http://blog.csdn.net/guolin_blog/article/details/53939176 在本系列的上一篇文章中,我们学习了Glide的基本用法,体验了这个图片加载框架的强大功能,以及它非常简便的API.还没有看过上一篇文章的朋友,建议先去阅读 Android图片加载框架最全解析(一),Glide的基本用法 . 在多数情况下,我们想要在界面上加载并展示一张图片只需要一行代码就能实现,如下所示: Glide.with(this).load(url).into(i

Python解析器源码加密系列之(二):一次使用标准c的FILE*访问内存块的尝试

摘要:由于近期打算修改Python解释器以实现pyc文件的加密/解密,出于保密的要求,解密之后的数据只能放在内存中,不能写入到文件中.但是后续的解析pyc文件的代码又只能接受FILE*作为入参,所以就提出了一种把通过FILE*来访问内存的需求,下文是针对这个需求的几个方面的尝试及其结论. 以下尝试的前提是:Win7 + VS2010. 在vc中,FILE其实就是_iobuf,定义如下: struct _iobuf { char *_ptr; //文件输入的下一个位置 int _cnt; //当前

HtmlAgilityPack --解析Html源码

最近项目需要从网络上抓取一下数据解析Html源码,奈何正则表达式难写,于是网上搜索找到了“ HtmlAgilityPack”类库,敏捷开发,果然效率非同寻常. 在此做笔记,写下心得,顺便给自己总结一下. 1. HtmlAgilityPack使用的是XPath进行路径搜索,如果对XML路径搜索很熟悉,用起来会得心应手 <?xml version="1.0" encoding="ISO-8859-1"?> <bookstore> <book

C语言解析JSON源码

2020-01-09 关键字:cJSON.linux JSON解析 JSON 是一种在互联网领域内很常用的轻量级数据交换协议. 它与 XML 的地位差不多,但就笔者而言,笔者更喜欢 JSON 的风格,因为它更符合我们的思维习惯,同样一份数据,JSON 格式的就是比 XML 要清晰明了一些. 最近笔者需要在 C语言 上解析 JSON 格式,在网上一顿找,找到一份很不错的开源代码.经过一阵研究与修改以后,终于变成了让笔者用的很顺手的 C语言 版 JSON 解析器. 现将这份经笔者小小修改过的代码记录

用Beautiful Soup解析html源码

#xiaodeng #python3 #用Beautiful Soup解析html源码 html_doc = """ <html> <head> <title>The Dormouse's story</title> </head> <body> <b>测试</b> <p class="title"> <b>The Dormouse's

EventBus 粘性事件,源码解析

1 Eeventbus 在处理消息通信的时候是比通常的出发办法好用的得,最新使用了eventbus 的粘性事件 首先你需要传递一个消息bean, EventBus.getDefault().postSticky(messageStatusResultBean); 2 调用上面的方法 就会把这个objoct 放到里面 private final Map<Class<?>, Object> stickyEvents; 3 在要处理的地方获取消息bean @Subscribe(stick

EventBus使用详情、源码分析和注意事项

基本介绍 EventBus主要用于事件的订阅和发布,主要功能是替代Intent,Handler,BroadCast在Fragment,Activity,Service,线程之间传递消息.官方文档介绍了EventBus的很多优点,归纳一下就是三个优点:小,快和方便.以下会围绕初步使用.使用进阶.源码分析和注意事项来讲解,如果需要快速上手,只要看完初步使用和注意事项就可以了. 初步使用 EventBus的使用非常简单,主要使用到以下三个方法: //注册EventBus EventBus.getDef