概述

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中移除

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

EventBus3.0 study的更多相关文章

  1. EventBus3.0源码解析

    本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...

  2. Android -- 从源码带你从EventBus2.0飚到EventBus3.0(一)

    1,最近看了不少的面试题,不管是百度.网易.阿里的面试题,都会问到EventBus源码和RxJava源码,而自己只是在项目中使用过,却没有去用心的了解它底层是怎么实现的,所以今天就和大家一起来学习学习 ...

  3. Android -- 从源码带你从EventBus2.0飚到EventBus3.0

    1,最近看了不少的面试题,不管是百度.网易.阿里的面试题,都会问到EventBus源码和RxJava源码,而自己只是在项目中使用过,却没有去用心的了解它底层是怎么实现的,所以今天就和大家一起来学习学习 ...

  4. Android事件总线(二)EventBus3.0源码解析

    1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...

  5. Android事件总线(一)EventBus3.0用法全解析

    前言 EventBus是一款针对Android优化的发布/订阅事件总线.简化了应用程序内各组件间.组件与后台线程间的通信.优点是开销小,代码更优雅,以及将发送者和接收者解耦.如果Activity和Ac ...

  6. Android之EventBus1.0 和EventBus3.0的使用详解

    当Android项目越来越庞大的时候,应用的各个部件之间的通信变得越来越复杂,那么我们通常采用的就是Android中的解耦组件EventBus.EventBus是一款针对Android优化的发布/订阅 ...

  7. EventBus3.0使用总结

    在Android中,接口回调已经能够处理掉大部分业务需求了,实在太变态的需求就用广播也能够完成,自己写的性能好出问题也好解决.....工作需要,不得不看看EventBus的用法,今天就来介绍一下学习经 ...

  8. Android中使用开源框架EventBus3.0实现Fragment之间的通信交互

    1.概述 在之前的博文中简单介绍过如何实现fragment之间的信息交互:<Android中Fragment与Activity之间的交互(两种实现方式)>,今天继续给大家介绍一种可以实现此 ...

  9. Android事件总线分发库EventBus3.0的简单讲解与实践

    Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...

随机推荐

  1. 使用Java正则表达式去掉Double类型的数据后面多余的0

    方法 /** * 使用java正则表达式去掉多余的.与0 * @param s * @return */ public static String subZeroAndDot(String s){ i ...

  2. MTK8127源码编译出现的错误及相关解决办法

    /** * date:2016/8/17 * author: Y.X .YANG */ 按照开发文档提示: 1.MTK提供的开发包目录下有若干个.aa .ab .ac ...的分压缩包.此时应当将这些 ...

  3. 学习笔记3-开发与运行(卸载)第一个ANDROID应用

    新建Android项目 1.      配置好Android坏境以后,新建项目选择Android Project. 2.      选择针对哪个平台开发的应用(Android2/Android4等) ...

  4. Android开发-Listview中显示不同的视图布局

    1. 使用场景 在重写ListView的BaseAdapter时,我们常常在getView()方法中复用convertView,以提高性能.convertView在Item为单一的同种类型布局时,能够 ...

  5. ntoskrnl符号在IDA中查看的问题

    最近发现x64的ntoskrnl.exe,如果直接在IDA中查看,会有一些函数IDA没有识别出来,比如

  6. Opencv2.4.9、VS2010配置及grabcut代码实例

    最近opencv库更新到了2.4.9,作为小码农的我紧跟时代的步伐,装了2.4.9这个库.以下是我的配置步骤: 1.从http://opencv.org/上下载opencv2.4.9文件,由于我用的是 ...

  7. 基于WAMP的Crossbario 安装入门

    简单学习和使用WAMP协议,Router 是crossbario, Client是Autobahn, 了解运作的流程. 测试环境是Centos6 虚拟机一台 目录为 /data/wamp/ ,用的是P ...

  8. JAVA之旅(十五)——多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止

    JAVA之旅(十五)--多线程的生产者和消费者,停止线程,守护线程,线程的优先级,setPriority设置优先级,yield临时停止 我们接着多线程讲 一.生产者和消费者 什么是生产者和消费者?我们 ...

  9. FSG报表打印报错,log文件显示java.sql.SQLException: No corresponding LOB data found

    报错信息: +---------------------------------------------------------------------------+ Plsql 程序的日志信息开始 ...

  10. Linux0.11 创建进程的过程分析--fork函数的使用

    /* * linux/kernel/fork.c * * (C) 1991 Linus Torvalds */ /* 注意:signal.c和fork.c文件的编译选项内不能有vc变量优化选项/Og, ...