源码阅读计划-EventBus

EventBus的api很简单:

public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                EventBus.getDefault().post(new MyEvent(123));
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        EventBus.getDefault().register(this);
    }

    @Override
    protected void onPause() {
        super.onPause();
        EventBus.getDefault().unregister(this);
    }

    @Subscribe(threadMode = ThreadMode.MAIN)
    public void onMyEvent(MyEvent event) {
        Log.d("testtest", "onMyEvent " + event.data);
    }
}

class MyEvent {
    public int data;

    MyEvent(int data) {
        this.data = data;
    }
}

注册监听的原理

坦白讲内部的实现原理也挺简单的,我们从注册开始看:

public void register(Object subscriber) {
    Class<?> subscriberClass = subscriber.getClass();
    // 从subscriber的class从查找回调方法
    List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
    synchronized (this) {
        for (SubscriberMethod subscriberMethod : subscriberMethods) {
            // SubscriberMethod里面是方法的反射,所以需要绑定回调方法和观察者对象
            subscribe(subscriber, subscriberMethod);
        }
    }
}

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
    Class<?> eventType = subscriberMethod.eventType;
    Subscription newSubscription = new Subscription(subscriber, subscriberMethod);
    // 使用Event的class做key,查找这个Event的所有观察者
    CopyOnWriteArrayList<Subscription> subscriptions = subscriptionsByEventType.get(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;
        }
    }
    ...
}

使用Finder从对象的Class中查找注册的回调方法的信息,使用Subscription将调用者与方法信息绑定起来,然后使用Event的Class作为key将它放到subscriptionsByEventType这个map中。由于同一种Event可能会有多个观察者,所以map的value是一个按优先级排序的List。

SubscriberMethod和Subscription的定义如下:

public class SubscriberMethod {
    final Method method;         // 回调方法
    final ThreadMode threadMode; // 回调的线程策略
    final Class<?> eventType;    // 监听的事件类型
    final int priority;          // 优先级
    final boolean sticky;        // 是否监听粘性事件
    ...
}

final class Subscription {
    final Object subscriber; //观察者的对象引用,可以用来反射调用subscriberMethod.method
    final SubscriberMethod subscriberMethod;
    ...
}

从上面可以大概猜出来,事件的分发实际是反射调用的方法。

也就是说当我们使用post方法发送Event的时候就能用Event的Class查找到所有的Subscription,通过Subscription的subscriber找到注册的对象、subscriberMethod找到注册的方法,接着就能使用反射去进行分发。

整个注册的流程大体上是比较清晰的,但是findSubscriberMethods的查找注册信息流程中有些小的细节也比较值得学习。

1.缓存

第一点是扫描完一个class之后会将监听的信息放入METHOD_CACHE缓存中,例如我们的demo,onResume的时候register,onPause的时候unregister,就能减少第二次onResume的耗时。

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    // METHOD_CACHE会保存查找过的subscriberClass的监听信息,防止重复查找耗时
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    ...
    subscriberMethods = findUsingInfo(subscriberClass);
    ...
    // 加入缓存,下次可以直接使用
    METHOD_CACHE.put(subscriberClass, subscriberMethods);
    return subscriberMethods;
    ...
}

2.APT生成索引

第二点就是由于反射遍历类方法去查找被@Subscribe修饰的方法比较耗时,所以可以使用apt编译时根据注解生成代码的方式直接生成索引:

private List<SubscriberMethod> findUsingInfo(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            // 优先从apt生成的索引查找,如果找不到就是用反射去遍历class,查找@Subscribe修饰的方法
            findState.subscriberInfo = getSubscriberInfo(findState);
            if (findState.subscriberInfo != null) {
                ...
            } else {
                // 使用反射查找
                findUsingReflectionInSingleClass(findState);
            }
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

apt创建索引的使用方法见官方文档

3.对象池减少内存碎片

第三就是FindState是一个辅助查找的工具类,为了避免应用初始化的时候多个类都在注册到EventBus,导致这个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();
}

// 回收
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;
}

消息分发原理

上面我们看完了注册,实际上分发的流程大概也能猜出来了。不过这里面还是有蛮多小细节同样值得学习的。

1.Event Queue

EventBus的Event分发并不是直接循环遍历观察者进行分发,而是先将Event放到队列中,然后再去队列里面拿出来分发。

PostingThreadState使用了ThreadLocal,每条线程都有自己的Event队列,而且isPosting能够记录当前的线程是否正在分发Event。

也就是说如果在Event的回调中再去post一个Event,并不会立刻分发,而是会等之前的Event都分发完之后在去分发。

public void post(Object event) {
    PostingThreadState postingState = currentPostingThreadState.get();
    List<Object> eventQueue = postingState.eventQueue;
    //将Event丢到队列中
    eventQueue.add(event);

    if (!postingState.isPosting) { // 是否正在分发,例如在监听的回调中post,isPosting就是true
        postingState.isMainThread = isMainThread();
        postingState.isPosting = true;
        if (postingState.canceled) {
            throw new EventBusException("Internal error. Abort state was not reset");
        }
        try {
            // 从队列中拿出Event进行分发,直至队列为空
            while (!eventQueue.isEmpty()) {
                postSingleEvent(eventQueue.remove(0), postingState);
            }
        } finally {
            postingState.isPosting = false;
            postingState.isMainThread = false;
        }
    }
}

这里有个小坑:threadMode是POSTING,只能代表回调是在post的线程调用的,并不能代表post方法里面会立即调用

2.eventInheritance

当一个观察者监听的是父类,如果post了一个子类的Event,默认情况下观察者是可以接收到这个子类的Event。这是因为默认情况下EventBusBuilder.eventInheritance为true:

boolean eventInheritance = true;

如果不想要这个功能我们可以设置成false。它的原理在postSingleEvent里面可以看到:

private void postSingleEvent(Object event, PostingThreadState postingState) throws Error {
    Class<?> eventClass = event.getClass();
    boolean subscriptionFound = false;
    if (eventInheritance) {
        // 如果eventInheritance为true,查找eventClass的父类、实现的interface等去分发
        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 {
        // eventInheritance为false则只分发这个eventClass
        subscriptionFound = postSingleEventForEventType(event, postingState, eventClass);
    }
    ...
}

postSingleEventForEventType方法就是根据Class查找register的对应Subscription,调用postToSubscription进行实际的分发操作

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) {
            ...
            postToSubscription(subscription, event, postingState.isMainThread);
            ...
        }
        return true;
    }
    return false;
}

3.threadMode原理

postToSubscription内部就会根据不同的threadMode在不同线程使用反射调用注册的方法:

private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
    switch (subscription.subscriberMethod.threadMode) {
        case POSTING:
            ..
            break;
        case MAIN:
            ...
            break;
        case MAIN_ORDERED:
            ...
            break;
        case BACKGROUND:
            ...
            break;
        case ASYNC:
            ...
            break;
        default:
            ...
    }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • EventBus源码阅读记录 EventBus是一个Android上用的消息分发的类库,非常灵活好用,主要的原理是...
    圣骑士wind阅读 643评论 0 6
  • 前言 成为一名优秀的Android开发,需要一份完备的知识体系,在这里,让我们一起成长为自己所想的那样~。 不知不...
    hpc阅读 566评论 0 0
  • EventBus 是一种用于Android的事件发布-订阅总线,用于代替BroadcastReceiver,可以简...
    ImWiki阅读 164评论 0 1
  • 简介 前面我学习了如何使用EventBus,还有了解了EventBus的特性,那么接下来我们一起来学习EventB...
    eirunye阅读 452评论 0 0
  • 16宿命:用概率思维提高你的胜算 以前的我是风险厌恶者,不喜欢去冒险,但是人生放弃了冒险,也就放弃了无数的可能。 ...
    yichen大刀阅读 5,979评论 0 4