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;
}
}
这里根据是否产生索引来得到订阅方法名
分为是否产生索引??这里没看懂,但是findUsingReflection
和findUsingInfo
里面都是通过一个while循环来找到具体的方法名
private List<SubscriberMethod> findUsingReflection(Class<?> subscriberClass) {
FindState findState = prepareFindState();
findState.initForSubscriber(subscriberClass);
while (findState.clazz != null) {
findUsingReflectionInSingleClass(findState);
findState.moveToSuperclass();
}
return getMethodsAndRelease(findState);
}
这里findUsingReflection
和findUsingInfo
都有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的更多相关文章
- EventBus3.0源码解析
本文主要介绍EventBus3.0的源码 EventBus是一个Android事件发布/订阅框架,通过解耦发布者和订阅者简化 Android 事件传递. EventBus使用简单,并将事件发布和订阅充 ...
- Android -- 从源码带你从EventBus2.0飚到EventBus3.0(一)
1,最近看了不少的面试题,不管是百度.网易.阿里的面试题,都会问到EventBus源码和RxJava源码,而自己只是在项目中使用过,却没有去用心的了解它底层是怎么实现的,所以今天就和大家一起来学习学习 ...
- Android -- 从源码带你从EventBus2.0飚到EventBus3.0
1,最近看了不少的面试题,不管是百度.网易.阿里的面试题,都会问到EventBus源码和RxJava源码,而自己只是在项目中使用过,却没有去用心的了解它底层是怎么实现的,所以今天就和大家一起来学习学习 ...
- Android事件总线(二)EventBus3.0源码解析
1.构造函数 当我们要调用EventBus的功能时,比如注册或者发送事件,总会调用EventBus.getDefault()来获取EventBus实例: public static EventBus ...
- Android事件总线(一)EventBus3.0用法全解析
前言 EventBus是一款针对Android优化的发布/订阅事件总线.简化了应用程序内各组件间.组件与后台线程间的通信.优点是开销小,代码更优雅,以及将发送者和接收者解耦.如果Activity和Ac ...
- Android之EventBus1.0 和EventBus3.0的使用详解
当Android项目越来越庞大的时候,应用的各个部件之间的通信变得越来越复杂,那么我们通常采用的就是Android中的解耦组件EventBus.EventBus是一款针对Android优化的发布/订阅 ...
- EventBus3.0使用总结
在Android中,接口回调已经能够处理掉大部分业务需求了,实在太变态的需求就用广播也能够完成,自己写的性能好出问题也好解决.....工作需要,不得不看看EventBus的用法,今天就来介绍一下学习经 ...
- Android中使用开源框架EventBus3.0实现Fragment之间的通信交互
1.概述 在之前的博文中简单介绍过如何实现fragment之间的信息交互:<Android中Fragment与Activity之间的交互(两种实现方式)>,今天继续给大家介绍一种可以实现此 ...
- Android事件总线分发库EventBus3.0的简单讲解与实践
Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...
随机推荐
- 看见的力量 – (I) 解题的思维
本文转自台湾李智桦老师的博客,原文地址 这篇文章:已经梗了我三个多星期了.这期间飞了二次大陆做演讲.往返几个大城市做教授敏捷开发运用在精实创业的课程.教材内容都是简体的,它们始终没有机会在国内用上,心 ...
- 02基于注解开发SpringMVC项目(jar包,异步,request,参数传递,多选的接收,Model传参,map传参,model传参,ajax,重定向,时间日期转换)
1 所需jar包 项目结构如下: 2 web.xml配置文件的内容如下: <?xmlversion="1.0"encoding="UTF-8"?&g ...
- 为你的MacOS App添加开机自启动(Swift)
猴子原创,欢迎转载.转载请注明: 转载自Cocos2Der-CSDN,谢谢! 原文地址: http://blog.csdn.net/cocos2der/article/details/52104828 ...
- JQuery实战--可以编辑的表格
廊坊下雪了,15年的第二场雪,比14的来的稍晚一些,停靠在11教门前的自行车,成了廊坊师范学院最美丽的风景线.还记得以前学习css的时候,就曾经接触过如何编写设计一些表格和表单的样式,例如如何设计表格 ...
- 套接字输入流——InputStream
输入缓冲装置里面必须要包含读取字符的通道,否则就谈不上缓冲了,这个通道就是InputStream,它属于jdk中java.io包的类,有了它我们就可以从源头读取字符,它的来源可以有多种多样,这里主要探 ...
- Java-IO之PrintStream(打印输出流)
PrintStream是打印输出流,继承于FilterOutputStream,PrintStream是用来装饰其他输出流,为其他输出流添加功能,方便他们打印出各种数据值表示形式.与其他输出流不同,P ...
- Fedora 19: How to resize/extend (LVM) partition?
Enlarge the disk using fdisk fdisk -l (to see the partition layout, typically we're dealing with /de ...
- java linux 项目经常无故被关闭 进程无故消息
布了几个项目.居然天天会自动的挂掉.急了.花时间解决了一下.总结方案如下: 1.磁盘满了.这大家都懂,清一下 2.tomcat在关闭的或是重启的时候,常常后台进程没有被关闭.需要用ps aux|gre ...
- iOS中 UITextView文本视图 技术分享
UITextView: 文本视图相比与UITextField直观的区别就是UITextView可以输入多行文字并且可以滚动显示浏览全文. UITextField的用处多,UITextView的用法也不 ...
- numpy教程:统计函数Statistics
http://blog.csdn.net/pipisorry/article/details/48770785 , , ] , '\n') 输出: True 当然可以设置度参数bias : int, ...