greenrobot/EventBus3.0使用及源码分析

简介

EventBus通过更少的代码和更高的质量为Android和Java提供的事件总线简化了活动,片段,线程,服务等之间的通信

正如EventBus website官网形容的那样:

(1)简化组件间的通信

(2)解耦事件发送器和接收器

(3)使用UI工件(例如活动、片段)和后台线程执行良好的操作

(4)避免复杂且容易出错的依赖关系和生命周期问题

(5)快;专门针对高性能进行优化

(6)很小(< 50 k jar)

(7)有100,000,000+安装的应用实践(证明有100,000,000+的应用替你踩过了坑)

(8)具有交付线程、用户优先级等高级功能

(9)我觉得通过注解使得方法名字更加自由

1.EventBus实现原理

    官网说:EventBus是Android和Java的开放源码库,使用发布者/订阅者模式(其实就是观察者模式)进行松散耦合。EventBus可以通过几行代码(简化代码、删除依赖项和加速应用程序开发)实现中央通信来解耦类

 

subscriber找到所有订阅方法和订阅方法参数

publisher组装消息对象,通过消息类和参数类对应,找到订阅方法,执行反射方法绑定消息

发布者/订阅者模式举个栗子: 微信公众号,你订阅了他,他发了推文,你就接收到消息

2.EventBus技术点

(1)线程安全的单例模式(教科书写法,同样还有静态代码实现和枚举实现)

(2)设计模式-- builder建造者模式

(3)注解使用

(4)反射使用

(5)AbstractProcessor注解处理器

(6)map put remove返回值(简化代码)

3.EventBus使用

官方Demo

(1) 基本操作:

Subscriber:

EventBus.getDefault().register(this);

@Subscribe
public void xxxx(TestEvent testEvent) {
}

Publisher:

EventBus.getDefault().post(new TestEvent()); 

(2)骚操作 :  通过注解处理器EventBusAnnotationProcessor,生成MyEventBusIndex.java

Subscriber:

EventBus eventBus = EventBus.builder().addIndex(new MyEventBusIndex()).ignoreGeneratedIndex(false).build();

eventBus.register(this);

@Subscribe
public void xxxx(TestEvent testEvent) {
}

Publisher:

eventBus.post(new TestEvent()); 

我们的项目中采用这种方式除了把module拷过去还要修改如下几点:

(1) EventbusAnnotationProcessor  build.gradle compile project(':eventbus')改为compile 'org.greenrobot:eventbus:3.1.1'

(2) app  build.gradle

defaultConfig {
    javaCompileOptions {
        annotationProcessorOptions {
            arguments = [eventBusIndex: 'org.greenrobot.eventbusperf.MyEventBusIndex']
        }
    }
}
dependencies {
    annotationProcessor project(':eventbus-annotation-processor')
}

(3)settings.gradle

include ':app'
include ':EventBusAnnotationProcessor'
project(":EventBusAnnotationProcessor").name = "eventbus-annotation-processor"

4.EventBus源码分析

按照publisher/subscriber模式来分析代码

4.1 subscriber  订阅消息

 

( 1 ~ 3 )单例模式结合建造者模式创建EventBus对象,生成mainThreadPoster,backgroundPoster, asyncPoster

 

(    4   )得到主线线程Looper,创建主线程Handler  HandlerPoster

(    5   )创建SubscriberMethodFinder对象

(6 ~ 15)递归订阅者及其父类们得到所有订阅消息方法,按照规则存入到集合subscriptionsByEventType, typesBySubscriber中

subscriber:      订阅者

method:          订阅者中@Subscribe注解的方法

threadMode:    订阅方法运行线程参数(POSTING,MAIN,MAIN_ORDERED,BACKGROUND,ASYNC)

evntType:        订阅方法的参数类名

priority:           方法优先级,subscriptions是按照priority进行插入,值越小就排越前,越早会被

sticky:             是否是粘性方法,配合粘性post方法,如果post在register之前,在register成功后也能保证收到订阅消息

这边有两个逻辑需要注意一下,(真的饶了我好久):

4.1.1递归订阅方法的方式

(1)!ignoreGeneratedIndex 配合List<SubscriberInfoIndex> subscriberInfoIndexes          

上文介绍 greenrobot官方demo栗子中实例化自动化编译生成MyEventBusIndex类,通过建造者builder方法addIndex最后赋值给了SubscriberMethodFinder对象的subscriberInfoIndexes集合.集合里面保存了订阅类,订阅方法,参数类,回调线程。

(2)ignoreGeneratedIndex   这个是我们利用默认构造器生成EventBus单例时会走这个方法

其实目的都是要找订阅类及其父类所有订阅方法,方法1是实例化EventBus对象时通过new MyEventBusIndex()获得相关字段,调用getSubscriberMethods反射获取订阅方法.方法2是直接利用反射解析当前订阅类及其父类获取订阅方法.方法1比方法2少了递归查找父类的逻辑,提高了效率,但是一般我们的项目类的层级不高,所以基本忽略不计.

List<SubscriberMethod> findSubscriberMethods(Class<?> subscriberClass) {
    List<SubscriberMethod> subscriberMethods = METHOD_CACHE.get(subscriberClass);
    if (subscriberMethods != null) {
        return subscriberMethods;
    }
    if (ignoreGeneratedIndex) {
        subscriberMethods = findUsingReflection(subscriberClass);
    } else {
        subscriberMethods = findUsingInfo(subscriberClass);
    }
    if (subscriberMethods.isEmpty()) {
        throw new EventBusException("Subscriber " + subscriberClass
                + " and its super classes have no public methods with the @Subscribe annotation");
    } else {
        METHOD_CACHE.put(subscriberClass, subscriberMethods);
        return subscriberMethods;
    }
}

4.1.2是否可以添加方法集合逻辑

判断条件:这边的逻辑分为两步骤:

第一步: 校验首次存入集合键订阅方法参数,值方法,的正确性.(greenrobot也说了自己是偏执狂,非要写这个恶心的逻辑)

第二步: 后来进来这个为标准,除非这个方法所在类是标准值方法所在类的子类或子接口才能被添加进去.我一开始觉得这个是没道理的,因为是从子类开始往父类找订阅方法,怎么会找到子类的子类呢?其实这里是逻辑需要,因为subscriberClassByMethodKey里面已经存入标准键值,没法通过存不存在来判断,只能通过isAssignableFrom将它排除.

效果:子类和父类都有相同参数的相同订阅方法,只有子类的接收到订阅消息

boolean checkAdd(Method method, Class<?> eventType) {
    // 2 level check: 1st level with event type only (fast), 2nd level with complete signature when required.
    // Usually a subscriber doesn't have methods listening to the same event type.
    Object existing = anyMethodByEventType.put(eventType, method);
    if (existing == null) {
        return true;
    } else {
        if (existing instanceof Method) {
            if (!checkAddWithMethodSignature((Method) existing, eventType)) {
                // Paranoia check
                throw new IllegalStateException();
            }
            // Put any non-Method object to "consume" the existing Method
            anyMethodByEventType.put(eventType, this);
        }
        return checkAddWithMethodSignature(method, eventType);
    }
}

private boolean checkAddWithMethodSignature(Method method, Class<?> eventType) {
    methodKeyBuilder.setLength(0);
    methodKeyBuilder.append(method.getName());
    methodKeyBuilder.append('>').append(eventType.getName());
    String methodKey = methodKeyBuilder.toString();
    Class<?> methodClass = method.getDeclaringClass();
    Class<?> methodClassOld = subscriberClassByMethodKey.put(methodKey, methodClass);
    if (methodClassOld == null || methodClassOld.isAssignableFrom(methodClass)) {
        // Only add if not already found in a sub class
        return true;
    } else {
        // Revert the put, old class is further down the class hierarchy
        subscriberClassByMethodKey.put(methodKey, methodClassOld);
        return false;
    }
}

4.2 publisher  发送消息(以MainThread为例)

(1   ~   2)创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程

(3   ~   6)创建主线程Poster

(7   ~   8)把消息对象存入线程间局部变量currentPostingThreadState中

( 9  ~ 18)得到订阅方法对象,发送方法在对应的线程中执行反射

关键方法理解

4.2.1策略模式实现线程模式

这边就是EventBus核心就是能让发送的消息运行在任意线程中

MAIN,MAIN_ORDERED  利用主线程Looper创建HandlerPoster对象,故能运行在主线程

BACKGROUND             利用线程池创建一个线程对象运行BackgroundPoster方法

ASYNC                        利用线程池创建一个线程对象运行AsyncPoster方法

BACKGROUND和ASYNC两个异步线程的区别:

    BACKGROUND线程run起来是一个死循环,只要一直在有消息发出,所有的消息都是在这一个线程消息中,如果没有消息发出了,循环会被打断,线程会休眠.有人和线程池申请开辟线程时,这个线程会被回收掉.而ASYNC线程代码很简单,只要有消息我就让线程池开辟一个线程给我,让我去执行反射方法绑定消息,执行完立马线程休眠,再来消息重复操作,所以这种方式发送的消息都不会是同一个线程,这也是为什么greenrobot要采用线程池,这边不停的在创建,回收线程,采用线程池能减少开销

public void enqueue(Subscription subscription, Object event) {
    PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
    synchronized (this) {
        queue.enqueue(pendingPost);
        if (!executorRunning) {
            executorRunning = true;
            eventBus.getExecutorService().execute(this);
        }
    }
}

@Override
public void run() {
    try {
        try {
            while (true) {
                PendingPost pendingPost = queue.poll(1000);
                if (pendingPost == null) {
                    synchronized (this) {
                        // Check again, this time in synchronized
                        pendingPost = queue.poll();
                        if (pendingPost == null) {
                            executorRunning = false;
                            return;
                        }
                    }
                }
                eventBus.invokeSubscriber(pendingPost);
            }
        } catch (InterruptedException e) {
            eventBus.getLogger().log(Level.WARNING, Thread.currentThread().getName() + " was interruppted", e);
        }
    } finally {
        executorRunning = false;
    }
}

 

    public void enqueue(Subscription subscription, Object event) {
        PendingPost pendingPost = PendingPost.obtainPendingPost(subscription, event);
        queue.enqueue(pendingPost);
        eventBus.getExecutorService().execute(this);
    }

    @Override
    public void run() {
        PendingPost pendingPost = queue.poll();
        if(pendingPost == null) {
            throw new IllegalStateException("No pending post available");
        }
        eventBus.invokeSubscriber(pendingPost);
    }

4.2.2 方法10

    你发送的消息对象可能subscriptionsByEventType key值(订阅方法参数类) 的子类或者子接口,如果没有这个逻辑是过滤不出来的,这边就是递归将发送消息的类,还有他的实现接口,还有他超类,超接口存在集合中,循环到subscriptionsByEventType中匹配,所以只要是订阅方法参数是发送消息的超类和超接口都会收到通知

private static List<Class<?>> lookupAllEventTypes(Class<?> eventClass) {
    synchronized (eventTypesCache) {
        List<Class<?>> eventTypes = eventTypesCache.get(eventClass);
        if (eventTypes == null) {
            eventTypes = new ArrayList<>();
            Class<?> clazz = eventClass;
            while (clazz != null) {
                eventTypes.add(clazz);
                addInterfaces(eventTypes, clazz.getInterfaces());
                clazz = clazz.getSuperclass();
            }
            eventTypesCache.put(eventClass, eventTypes);
        }
        return eventTypes;
    }
}

5.1粘性消息

publisher 调用postSticky方法   Subscriber 订阅方法必须要将sticky设置true

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        EventBus.getDefault().postSticky(new Event(1));
        EventBus.getDefault().register(this);
    }

    @Subscribe(sticky = true)
    public void handleEvent(Event event) {
        Log.e("wjq","MainActivity = " + event.id + "");
    }

}

    有时候发送消息在订阅之前,按照现在的逻辑,先把所有订阅方法检索出来,当有消息需要发出时,匹配方法反射的思路是不行.greenrobot的思路是你发送粘性消息我把你保存在内存中,当有人订阅消息时先检查他是不是粘性订阅方法,再按照原有逻辑执行

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

 

private void subscribe(Object subscriber, SubscriberMethod subscriberMethod) {
Class<?> eventType = subscriberMethod.eventType;
 ....
    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);
        }
    }
}


 

  • 4
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
EventBus是一种用于Android应用程序中发布/订阅事件的库。它遵循发布-订阅模式,允许不同组件之间进行松散耦合的通信。 在基于EventBus 3.0的APP开发中,你可以按照以下步骤进行: 1. 添加EventBus依赖 在项目的build.gradle文件中添加以下代码: ``` dependencies { implementation 'org.greenrobot:eventbus:3.2.0' } ``` 2. 创建事件类 创建一个事件类,它将包含你需要发送和接收的数据。例如: ``` public class MessageEvent { public final String message; public MessageEvent(String message) { this.message = message; } } ``` 3. 注册订阅者 在需要接收事件的组件中,注册订阅者。例如,在Activity中: ``` @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { EventBus.getDefault().unregister(this); super.onStop(); } ``` 4. 发布事件 在需要发送事件的组件中,发布事件。例如,在Activity中: ``` EventBus.getDefault().post(new MessageEvent("Hello, world!")); ``` 5. 处理事件 在订阅者中,创建一个方法来处理接收到的事件。例如,在Activity中: ``` @Subscribe(threadMode = ThreadMode.MAIN) public void onMessageEvent(MessageEvent event) { // Do something with the event Toast.makeText(this, event.message, Toast.LENGTH_SHORT).show(); } ``` 以上就是基于EventBus 3.0的APP开发的基本步骤。通过使用EventBus,你可以轻松地在不同组件之间传递数据,从而实现应用程序中的松散耦合通信。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值