EventBus3.0 study

简介:

概述

eventbus出来很久了,最近想用一下eventbus,自己对着一些博客撸了一个demo,发现竟然crash了,然后去看看源码发现3.0的eventbus有了很多改动。技术变化真快,得保持谦虚的态度,踏踏实实的学习。正好今天内部群里发了一个如何新技术的学习图,感觉挺好的 
这里写图片描述

作为一个Android入门小白还是要多学点。

基本概念

eventbus是一个是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间、组件与后台线程间的通信。比如请求网络,等网络返回时通过Handler或Broadcast通知UI,两个Fragment之间需要通过Listener通信,这些需求都可以通过EventBus实现,作为一个消息总线主要有三个组成部分:

  • 事件(Event)
  • 事件订阅者(Subscriber)
  • 事件发布者(Publisher)

官方一张图可以很好地说明这三者关系 
这里写图片描述

细细想来eventbus确实比android的回调函数要强很多,回调只能回到特定的类中,而Eventbus只要在某类中监听了某个消息,无论在哪个类中,只要有消息发出,监听类都会得到执行,而且比一些广播啥的更加高效。Eventbus主要功能是替代Intent,Handler,BroadCast(onreceive不能超过10s)在Fragment,Activity,Service,线程之间传递消息.优点是开销小,代码更优雅。以及将发送者和接收者解耦。不过也看到一些人说Eventbus用多了会使代码显得比较混乱。

如何使用

添加依赖 compile 'org.greenrobot:eventbus:3.0.0' 
- 定义传递的数据结构 
这是传递的一个载体,由于传递调用post需要一个object,因此定义的类也可以是一个空类

public class TestEvent {
    private String mMsg;

    public TestEvent(String msg) {

        mMsg = msg;
    }

    public String getMsg() {
        return mMsg;
    }
}
  • 注册与注销 
    在需要接受消息的类中进行注册,我需要在MainActvity中接收消息,就在onCreate中注册
EventBus.getDefault().register(this);

在onDestroy中注销

 EventBus.getDefault().unregister(this);
  • 订阅函数 
    在注册的类中书写处理函数,以前都是说要以onEvent开头分为4个 
    1、onEvent 
    2、onEventMainThread 
    3、onEventBackgroundThread 
    4、onEventAsync 
    对应着在不同线程处理,其实3.0的eventbus抛弃了这种写法,你可以自定义函数名,但是,必须要在定义的函数上加上subscribe的注解,注解里面可以定义threadMode ,是否是sticky的,优先级,订阅函数是靠参数区分而不是靠函数名。比如这里就是在MAIN thread中
@Subscribe(threadMode = ThreadMode.MAIN)
    public void doMain(TestEvent event) {
        String msg = "onEventMainThread get the msg: \n" + event.getMsg();
        tv_msg.setText(msg);
        Log.d("thread main", Thread.currentThread().getName());
        Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
    }

关于这几个参数这里介绍下: 
threadMode: 
在EventBus中的观察者通常有四种线程模型,分别是PostThread(默认)、MainThread、BackgroundThread与Async。

PostThread:如果使用事件处理函数指定了线程模型为PostThread,那么该事件在哪个线程发布出来的,事件处理函数就会在这个线程中运行,也就是说发布事件和接收事件在同一个线程。在线程模型为PostThread的事件处理函数中尽量避免执行耗时操作,因为它会阻塞事件的传递,甚至有可能会引起ANR。 
MainThread:如果使用事件处理函数指定了线程模型为MainThread,那么不论事件是在哪个线程中发布出来的,该事件处理函数都会在UI线程中执行。该方法可以用来更新UI,但是不能处理耗时操作。 
BackgroundThread:如果使用事件处理函数指定了线程模型为BackgroundThread,那么如果事件是在UI线程中发布出来的,那么该事件处理函数就会在新的线程中运行,如果事件本来就是子线程中发布出来的,那么该事件处理函数直接在发布事件的线程中执行。在此事件处理函数中禁止进行UI更新操作。 
Async:如果使用事件处理函数指定了线程模型为Async,那么无论事件在哪个线程发布,该事件处理函数都会在新建的子线程中执行。同样,此事件处理函数中禁止进行UI更新操作。

-sticky: 
类似粘性广播那样,的粘性事件,简单来说就是发送该事件之后订阅该事件也可以收到订阅前的消息(只能收到最近的一条消息) 
-priorty: 
默认优先级为0,只有在同一个threadmode中优先级越大越先收到订阅事件。

  • 发布函数 
    通过post或者postStcky来发布一条事件,如下,在TestActvity1中,点击按钮就发布一条事件
 public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_msgpost:
                Log.d("thread send", Thread.currentThread().getName());
                EventBus.getDefault().post(new TestEvent("testEvent1 msg send byTestAvtivity1"));
                break;
        }

    }

实例

代码下载传送门 
这里做了俩个功能,MianActvity中只有俩个button 测试类名(btn_act1)、模拟广播的传递链(btn_act2), 
MainActivty的订阅函数

   @Subscribe(threadMode = ThreadMode.MAIN)
    public void doMain(TestEvent event) {
        String msg = "onEventMainThread get the msg: \n" + event.getMsg();
        tv_msg.setText(msg);
        Log.d("thread main", Thread.currentThread().getName());
        Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
    }

    @Subscribe(threadMode = ThreadMode.BACKGROUND)
    public void doBack(TestEvent event) {
        Log.d("thread back", Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.ASYNC)
    public void doAsync(TestEvent event) {
        Log.d("thread async", Thread.currentThread().getName());
    }

    @Subscribe(threadMode = ThreadMode.POSTING)
    public void dopost(TestEvent event) {
        Log.d("thread post", Thread.currentThread().getName());
    }

在跳转到TestActvity1中点击按钮发布,

 public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_msgpost:
                Log.d("thread send", Thread.currentThread().getName());
                EventBus.getDefault().post(new TestEvent("testEvent1 msg send byTestAvtivity1"));
                break;
        }

    }

TestActivy1点击之后从打印中可看到如下日志 
这里写图片描述 
这也证明了订阅函数是靠参数区分而不是靠函数名 ,在MainActvity中订阅函数名称不同但是入参一样都是TestEvent。

btn_act2中发布函数中模拟发起网络请求

  public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btn_msgpost2:
                EventBus.getDefault().post(new TestEvent2(" net request--send by TestAvtivity2"));
                break;
        }

    }

MainActivty的链式订阅函数

@Subscribe
    public void doReceive(TestEvent2 event2) {
        String msg = " receive the msg: \n" + event2.getMsg();
        tv_msg.setText(msg);
        EventBus.getDefault().post(new TestEvent3(" Net Datas"));
        tv_step2.setText("request Net Data ing…… ");
    }

    @Subscribe
    public void showNetData(TestEvent3 event3) {
        String msg = "show data:\n" + event3.getMsg();
        tv_step3.setText(msg);

    }

这里写图片描述 
这比回调或者广播高效多了

源码分析

  • 注册
EventBus.getDefault().register(this);
  /** Convenience singleton for apps using a process-wide EventBus instance. */
    public static EventBus getDefault() {
        if (defaultInstance == null) {
            synchronized (EventBus.class) {
                if (defaultInstance == null) {
                    defaultInstance = new EventBus();
                }
            }
        }
        return defaultInstance;
    }

    public static EventBusBuilder builder() {
        return new EventBusBuilder();
    }

在getdefault中使用双重枷锁,放止并发的产生,在register中去除了之前的4个参数的方法(Object subscriber, String methodName, boolean sticky, int priority)这些都移到了注解中,然后开始register方法

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

然后获取传入class对象subscriberMethodFinder.findSubscriberMethods(subscriberClass)获取到注解的订阅函数

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

这里根据是否产生索引来得到订阅方法名 
分为是否产生索引??这里没看懂,但是findUsingReflectionfindUsingInfo里面都是通过一个while循环来找到具体的方法名

private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
        FindState findState = prepareFindState();
        findState.initForSubscriber(subscriberClass);
        while (findState.clazz != null) {
            findUsingReflectionInSingleClass(findState);
            findState.moveToSuperclass();
        }
        return getMethodsAndRelease(findState);
    }

这里findUsingReflectionfindUsingInfo都有findState.moveToSuperclass();这里EventBus对继承的父类也会进行查找注册函数,即子类注册一次即可,父类直接使用 @Subscribe来产生订阅函数即可。

findUsingReflectionInSingleClass(findState);是二者核心函数

 private void findUsingReflectionInSingleClass(FindState findState) {
        Method[] methods;
        try {
            // This is faster than getMethods, especially when subscribers are fat classes like Activities
            methods = findState.clazz.getDeclaredMethods();
        } catch (Throwable th) {
            // Workaround for java.lang.NoClassDefFoundError, see https://github.com/greenrobot/EventBus/issues/149
            methods = findState.clazz.getMethods();
            findState.skipSuperClasses = true;
        }
        for (Method method : methods) {
            int modifiers = method.getModifiers();
            // 事件处理方法必须为public,这里过滤掉所有非public方法
            if ((modifiers & Modifier.PUBLIC) != 0 && (modifiers & MODIFIERS_IGNORE) == 0) {
                Class<?>[] parameterTypes = method.getParameterTypes();
                // 事件处理方法必须只有一个参数
                if (parameterTypes.length == 1) {
                    Subscribe subscribeAnnotation = method.getAnnotation(Subscribe.class);
                    if (subscribeAnnotation != null) {
                        Class<?> eventType = parameterTypes[0];
                        if (findState.checkAdd(method, eventType)) {
                            ThreadMode threadMode = subscribeAnnotation.threadMode();
                            findState.subscriberMethods.add(new SubscriberMethod(method, eventType, threadMode,
                                    subscribeAnnotation.priority(), subscribeAnnotation.sticky()));
                        }
                    }
                } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                // 如果某个方法加了@Subscribe注解,并且不是1个参数,则抛出EventBusException异常
                    String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                    throw new EventBusException("@Subscribe method " + methodName +
                            "must have exactly 1 parameter but has " + parameterTypes.length);
                }
            } else if (strictMethodVerification && method.isAnnotationPresent(Subscribe.class)) {
                String methodName = method.getDeclaringClass().getName() + "." + method.getName();
                throw new EventBusException(methodName +
                        " is a illegal @Subscribe method: must be public, non-static, and non-abstract");
            }
        }
    }

可以看出,这里该方法主要作用就是找出subscriberClass类以及subscriberClass的父类中所有的事件处理方法(添加了@Subscribe注解,访问修饰符为public并且只有一个参数)。值得注意的是:如果子类与父类中同时存在了相同事件处理函数,则父类中的不会被添加到subscriberMethods。

  • post
/** Posts the given event to the event bus. */
    public void post(Object event) {
        PostingThreadState postingState = currentPostingThreadState.get();
        List<Object> eventQueue = postingState.eventQueue;
        eventQueue.add(event);

        if (!postingState.isPosting) {
         // 标识post的线程是否是主线程
            postingState.isMainThread = Looper.getMainLooper() == Looper.myLooper();
            postingState.isPosting = true;
            if (postingState.canceled) {
                throw new EventBusException("Internal error. Abort state was not reset");
            }
            try {
                while (!eventQueue.isEmpty()) {
                 // 循环处理eventQueue中的event对象
                    postSingleEvent(eventQueue.remove(0), postingState);
                }
            } finally {
             // 重置postingState的一些标识信息
                postingState.isPosting = false;
                postingState.isMainThread = false;
            }
        }
    }

其中currentPostingThreadState是一个ThreadLocal类型,里面存储了PostingThreadState;

  private final ThreadLocal<PostingThreadState> currentPostingThreadState = new ThreadLocal<PostingThreadState>() {
        @Override
        protected PostingThreadState initialValue() {
            return new PostingThreadState();
        }
    };

PostingThreadState()中则包括post队列的一些信息

    /** For ThreadLocal, much faster to set (and get multiple values). */
    final static class PostingThreadState {
        final List<Object> eventQueue = new ArrayList<Object>();
        boolean isPosting;
        boolean isMainThread;
        Subscription subscription;
        Object event;
        boolean canceled;
    }
  • 取消事件初注册
    /** 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) {
                unsubscribeByEventType(subscriber, eventType);
            }
            typesBySubscriber.remove(subscriber);
        } else {
            Log.w(TAG, "Subscriber to unregister was not registered before: " + subscriber.getClass());
        }
    }

这里所做的工作只是将注册的事件从Eventbus中移除

水平有限这里只是简单的分析了一下源码,了解大概而已。


转载:http://blog.csdn.net/xsf50717/article/details/51058062

目录
相关文章
|
23天前
|
Java Android开发
EventBus简单介绍
EventBus简单介绍
26 0
EventBus简单使用
EventBus简单使用
151 0
DHL
|
Java API Android开发
EventBus3.1用法详解
EventBus是Android和Java的发布/订阅事件总线。从EventBus3.1开始支持普通Java(非android)项目。GitHub的地址
DHL
206 0
EventBus3.1用法详解
|
缓存 安全 UED
RxJava 的 Subject
RxJava 的 Subject
103 0
|
搜索推荐
【EventBus】EventBus 源码解析 ( EventBus 构建 | EventBus 单例获取 | EventBus 构造函数 | EventBus 构建者 )
【EventBus】EventBus 源码解析 ( EventBus 构建 | EventBus 单例获取 | EventBus 构造函数 | EventBus 构建者 )
105 0
【EventBus】EventBus 使用示例 ( 最简单的 EventBus 示例 )
【EventBus】EventBus 使用示例 ( 最简单的 EventBus 示例 )
167 0
【EventBus】EventBus 使用示例 ( 最简单的 EventBus 示例 )
|
Java
【EventBus】Subscribe 注解分析 ( Subscribe 注解属性 | threadMode 线程模型 | POSTING | MAIN | MAIN_ORDERED | ASYNC)
【EventBus】Subscribe 注解分析 ( Subscribe 注解属性 | threadMode 线程模型 | POSTING | MAIN | MAIN_ORDERED | ASYNC)
648 0
|
Kotlin 容器
EventBus3.1.1 解决 Caused by: org.greenrobot.eventbus.EventBusException: Subscriber class ... and its super classes have no public methods with the @Sub
      小菜今天自己写测试 Demo 时,需要用到 EventBus,目前集成 3.1.1 版本,集成的方式很简单,在某个 Fragment 实践应用中,却一直报入下错: Caused by: org.
12515 0
|
消息中间件 存储 调度
看eShopOnContainers学一个EventBus
最近在看微软eShopOnContainers 项目,看到事件总线觉得不错,和大家分享一下 看完此文你将获得什么? eShop中是如何设计事件总线的 实现一个InMemory事件总线eShop中是没有InMemory实现的,这算是一个小小小的挑战 发布订阅模式 发布订阅模式可以让应用程序组件之间解耦,这是我们使用这种模式最重要的理由之一,如果你完全不知道这个东西,建议你先通过搜索引擎了解一下这种模式,网上的资料很多这里就不再赘述了。
1314 0