源码阅读计划-EventBus

作者: 嘉伟咯 | 来源:发表于2021-08-12 23:37 被阅读0次

    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:
                ...
        }
    }
    

    相关文章

      网友评论

        本文标题:源码阅读计划-EventBus

        本文链接:https://www.haomeiwen.com/subject/xngrbltx.html