源码-EventBus粘性事件的实现原理浅析

1、EventBus粘性事件的实现原理浅析

1.1 EventBus的简介和架构

提到EventBus,相信大多数安卓开发者都用过,它是一个为Android 设计的一个基于观察者模式的发布/订阅事件总线框架,将事件的接收者和发送者分开,简化了组件之间的通信操作,使用非常简单,源码实现只有一两千行,也很适合学习。

在这里插入图片描述

EventBus内部利用handler机制实现了跨线程通信,因此不需要我们自己利用handler机制在线程间切换数据(注意一点,EventBus不能实现跨进程通信,因为EventBus的单例仅在一个进程内有效)。

1.2 什么是EventBus粘性事件

粘性事件是EventBus里面一个比较有特色的功能,什么是粘性事件呢?要想解答这个问题,我们要从普通事件说起。

public class EventBusTestActivity extends AppCompatActivity {

    private ActivityEventBusTestBinding bind;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        bind=ActivityEventBusTestBinding.inflate(getLayoutInflater());
        setContentView(bind.getRoot());
        //注册总线
        EventBus.getDefault().register(this);

        bind.btnSendMsg.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
            	//发送数据
                EventBus.getDefault().post(new MyMessage("普通消息"));
            }
        });
    }
    class MyMessage{
        final String msg;
        public MyMessage(String msg) {
            this.msg = msg;
        }
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        //取消总线注册
        EventBus.getDefault().unregister(this);
    }
}

总体上,一个Activity的普通EventBus的事件的完整周期是这样的:

注册到总线 -> 发送消息 ->销毁消息

看上去很完美,但是,假如有两个activity呢?ActivityA发送消息,ActivityB也想接收到,但是ActivityA发送消息时,ActivityB尚未创建,那就很尴尬了,粘性事件就是为了解决这样的问题的。

使用粘性事件,可以在订阅的activity创建之前发出,在创建完成后再接收。

ActivityA创建,注册总线->点击按钮,发送事件->ActivityB创建,消费事件

1.3 粘性事件实现原理

具体是怎么实现的呢?首先我们看一下粘性事件的发送函数

	private final Map<Class<?>, Object> stickyEvents;
	...
	EventBus(EventBusBuilder builder) {
        ...
        stickyEvents = new ConcurrentHashMap<>();
        ...
    }
	...
    public void postSticky(Object event) {
        synchronized (stickyEvents) {
            stickyEvents.put(event.getClass(), event);
        }
        // Should be posted after it is putted, in case the subscriber wants to remove immediately
        post(event);
    }
    
    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
            postingState.isMainThread = isMainThread();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

可以看到,这里仅仅是将粘性事件放到了一个ConcurrentHashMap中保存,然后又调用了post来发送消息,可以保证消息的正常发送。但是在这里我们并没有看到eventbus是如何在目标activity创建之后,发送消息的。其实奥秘在activity注册EventBus的那部分:

   public void register(Object subscriber) {
        Class<?> subscriberClass = subscriber.getClass();
        List<SubscriberMethod> subscriberMethods = subscriberMethodFinder.findSubscriberMethods(subscriberClass);
        synchronized (this) {
            for (SubscriberMethod subscriberMethod : subscriberMethods) {
                subscribe(subscriber, subscriberMethod);
            }
        }
    }

    // Must be called in synchronized block
    private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
       	...
        if (subscriberMethod.sticky) {
            if (eventInheritance) {
                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);
            }
        }
    }
    private void checkPostStickyEventToSubscription(Subscription newSubscription, Object stickyEvent) {
        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, isMainThread());
        }
    }
    
    private void postToSubscription(Subscription subscription, Object event, boolean isMainThread) {
        switch (subscription.subscriberMethod.threadMode) {
            case POSTING:
                invokeSubscriber(subscription, event);
                break;
            case MAIN:
                if (isMainThread) {
                    invokeSubscriber(subscription, event);
                } else {
                    mainThreadPoster.enqueue(subscription, event);
                }
                break;
            case MAIN_ORDERED:
                if (mainThreadPoster != null) {
                    mainThreadPoster.enqueue(subscription, event);
                } else {
                    // temporary: technically not correct as poster not decoupled from subscriber
                    invokeSubscriber(subscription, event);
                }
                break;
            case BACKGROUND:
                if (isMainThread) {
                    backgroundPoster.enqueue(subscription, event);
                } else {
                    invokeSubscriber(subscription, event);
                }
                break;
            case ASYNC:
                asyncPoster.enqueue(subscription, event);
                break;
            default:
                throw new IllegalStateException("Unknown thread mode: " + subscription.subscriberMethod.threadMode);
        }
    }

看了这段代码估计大家就恍然大悟了,原来,在注册时,检测到存在粘性事件(具体存放位置是粘性事件发布时,加入的粘性事件池)时,就直接对消息进行发布了,这就是为什么可以在activity创建前发布粘性事件,当创建时可以接收到消息的原理。

register->subscribe->checkPostStickyEventToSubscription->postToSubscription

1.4 关于粘性事件其他的小问题

你可能还想问,当发送粘性事件时,目标activity已经创建了,那会发生什么呢?会收到两次重复的消息吗?答案显然是否定的,因为在目标activity已经创建后,不再执行register函数,自然也没法出现两条重复的信息,假如你不小心写了两次EventBus.getDefault().register(this),也没关系,因为EventBus在类注册后,就不再重复注册了。

上面的结论都可以看着EventBus的源码捋一捋。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值