顾名思义,AndroidEventBus是一个Android平台的事件总线框架,它简化了Activity、Fragment、Service等组件之间的交互,很大程度上降低了它们之间的耦合,使我们的代码更加简洁,耦合性更低,提升了我们的代码质量。但它能做的却不仅限于这些。经过定制,它能完成很多有意思的功能,那么究竟该怎么做呢?就让我们一起往下看吧。

不堪回首的痛

首先,让我们先来看看这么一个场景:你是否在开发的过程中遇到过从Activity-A跳转到Activity-B,然后需要在Activity-B处理完某些工作之后回调Activity-A中的某个函数,但Activity又不能手动创建对象来设置一个Listener的情况?或者遇到在某个Service中更新Activity或Fragment中的界面等组件之间的交互问题……

一经思考,你会发现Android中的Activity、Fragment、Service之间的交互是比较麻烦的,可能我们第一想到的是使用广播接收器来在它们之间进行交互。如上文所说,在Activity-B中发一个广播,在Activity-A中注册一个广播接收器来接收该广播。但使用广播接收器稍显麻烦,如果你要将一个实体类当作数据在组件之间传递,那么该实体类还得实现序列化接口,这个成本实在有点高!如代码1所示。

  1. class ActivityA extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. // ActivityA中注册广播接收器
  6. registerReceiver(new BroadcastReceiver() {
  7. @Override
  8. public void onReceive(Context context, Intent intent) {
  9. User person = intent.getParcelableExtra("user") ;
  10. }
  11. }, new IntentFilter("my_action")) ;
  12. }
  13. // ......
  14. }
  15. // ActivityB中发布广播
  16. class ActivityB extends Activity {
  17. @Override
  18. protected void onCreate(Bundle savedInstanceState) {
  19. super.onCreate(savedInstanceState);
  20. // 发布广播
  21. Intent intent  = new Intent("my_    action");
  22. intent.putExtra("user", new User("mr.simple")) ;
  23. sendBroadcast(intent);
  24. }
  25. // ......
  26. }
  27. // 实体类需要实现序列化
  28. class User implements Parcelable {
  29. String name ;
  30. public User(String aName) {
  31. name = aName ;
  32. }
  33. // 代码省略
  34. @Override
  35. public void writeToParcel(Parcel dest, int flags) {
  36. dest.writeString(name);
  37. }
  38. }

代码1

是不是有很麻烦的感觉?我们再来看一个示例,在开发过程中,我们经常要在子线程中做一些耗时操作,然后将结果更新到UI线程,除了AsyncTask之外,Thread加Handler是我们经常用的手段。如代码2所示。

  1. class MyActivity extends Activity {
  2. Handler mHandler = new Handler () {
  3. public void handleMessage(android.os.Message msg) {
  4. if ( msg.what == 1 ) {
  5. User user = (User)msg.obj ;
  6. // do sth
  7. }
  8. };
  9. } ;
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. // code ......
  14. new Thread(
  15. new Runnable() {
  16. public void run() {
  17. // do sth
  18. User newUser = new User("simple") ;
  19. Message msg = mHandler.obtainMessage() ;
  20. msg.what = 1 ;
  21. msg.obj = newUser ;
  22. mHandler.sendMessage(msg) ;
  23. }
  24. }).start();
  25. }
  26. }

代码2

是不是依然相当麻烦?当然你也可以使用AsyncTask来简化操作,但AsyncTask的几个泛型参数让你的代码看起来并不那么简洁,因此GitHub上出现了TinyTask、SimpleTask这些开源库来简化AsyncTask的使用。而这些,使用AndroidEventBus都可以很好地解决!

下面就让我们来领悟一下AndroidEventBus的强大魅力吧。

初见AndroidEventBus

使用AndroidEventBus简单概括只有三个步骤:

  • 将对象注册到AndroidEventBus中;
  • 使用@Subcriber标注订阅函数(只能有一个参数);
  • 通过post函数发布事件。

接下来就是注册订阅对象,如代码3所示。

  1. public class MainActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. setContentView(R.layout.activity_main);
  6. // 将对象注册到事件总线中, ****** 注意要在onDestory中进行注销 ****
  7. EventBus.getDefault().register(this);
  8. }
  9. @Override
  10. protected void onDestroy() {
  11. super.onDestroy();
  12. // ****** 不要忘了进行注销 ****
  13. EventBus.getDefault().unregister(this);
  14. }
  15. // 代码省略
  16. }

代码3

在onCreate中注册之后,MainActivity就可以添加订阅函数来接收消息了。需要注意的是在onDestory中需要将MainActivity从事件总线中注销。通过AndroidEventBus你可以去除Activity、Fragment、Service等组件的回调,减少了耦合,简化了代码。

事件订阅函数

事件订阅需要使用@Subscriber注解进行标识,且订阅函数的参数必须为一个。事件总线凭借参数类型和@Subscriber注解的tag值来标识订阅函数的唯一性。当用户发布事件时,总线库会根据事件类型和tag来查找符合要求的订阅函数,并且将这些订阅函数执行在对应的线程中。我们先来看看代码4的订阅函数示例。

  1. public class MainActivity extends Activity {
  2. // 代码省略
  3. @Subcriber(tag = "csuicide")
  4. private void csuicideMyself(String msg) {
  5. // do sth
  6. finish();
  7. }
  8. @Subcriber(mode = ThreadMode.MAIN)
  9. private void toastMsgFromEvent(String msg) {
  10. // do sth
  11. }
  12. @Subcriber(tag = "async", mode = ThreadMode.ASYNC)
  13. private void executeAsync(final String msg) {
  14. // do sth
  15. }
  16. // 代码省略
  17. }

代码4

在代码4中,我们为MainActivity添加了以下三个订阅函数:

  • csuicideMyself:该订阅函数执行在主线程,接收事件的类型为String,tag为csuicide。当用户发布一个事件类型为String,且tag为csuicide的事件时将会触发该方法。
  • toastMsgFromEvent:该订阅函数也是执行在主线程,事件类型为String,且tag为默认。当用户发布一个事件类型为String,且tag为默认的事件时将会触发该方法。
  • executeAsync:该订阅函数也是执行在一个异步线程,事件类型为String,且tag为async。当用户发布一个事件类型为String,且tag为async的事件时将会触发该方法。

从上述的描述中我们可以知道,事件接收函数主要有两个约束:事件类型和tag(类似于Intent中的Action)。添加tag是因为在事件类型一样时,如果投递一个消息,那么单纯以事件类型(例如String)作为投递依据,那么多个参数为String的订阅函数将会被触发,这极大地降低了灵活性。

发布事件

参数1为事件类型,无tag:EventBus.getDefault().post(这是一个执行在异步线程的事件);参数2为tag,tag的类型为String,类似Intent的Action:EventBus.getDefault().post(这是一个执行在异步线程的事件:“async”)。

发布事件时可以构造任意类型的事件,如果没有tag则该参数可以省略。发布事件后,AndroidEventBus会根据事件类型和tag到已注册的订阅对象中查找符合要求的订阅函数,例如投递的第二个事件类型为String、tag为async,那么在MainActivity中符合要求的订阅函数就是:

  1. @Subcriber(tag = "async", mode = ThreadMode.ASYNC)
  2. private void executeAsync(final String msg) {
  3. // do sth
  4. }

AndroidEventBus的ThreadMode

在上述代码中有一段代码是这样的:

  1. @Subcriber(mode = ThreadMode.MAIN)
  2. private void toastMsgFromEvent(String msg) {
  3. }

这个mode可是大有来头,它指定这个事件接收函数执行在哪个线程中。具体有如下三个选项:

  • ThreadMode.MAIN,事件接收函数执行在UI线程;
  • ThreadMode.POST,事件在哪个线程发布,接收函数就执行在哪个线程;
  • ThreadMode.ASYNC,事件执行在一个独立的异步线程中。

图1中,事件接收函数就执行在异步线程。通过这几个线程模型,我们就可以定制接收函数的执行线程。这样我们就可以使用AndroidEventBus做很多事了。比如发布一个事件,在这个事件接收函数中进行耗时操作;或下载图片、进行HTTP请求、I/O操作等,以及替换Thread、AsyncTask等组件。不过,AndroidEventBus的功能远不止于此,下面我们就看看如何进行更高端的操作。

图1  接收函数执行在异步线程中

还可以怎么玩?

退出应用的另类实现

在Android应用开发中,有些情况下我们需要可以直接退出程序。但问题是,回退栈中含有其他的Activity存在,直接使用返回键并不能退出应用。此时我们常见的做法是再自定义一个Application子类,在子类中维护一个Activity的列表,然后在进入Activity时,将Activity添加到列表中,在Activity销毁之前将自己从Application子类的列表中移除。在需要退出应用时遍历Application子类的Activity列表,然后调用每个Activity的finish函数。那我们看看AndroidEventBus怎么实现这个功能。如代码5所示。

  1. public class CsuicideActivity extends Activity {
  2. @Override
  3. protected void onCreate(Bundle savedInstanceState) {
  4. super.onCreate(savedInstanceState);
  5. // 将对象注册到事件总线中, ****** 注意要在onDestory中进行注销 ****
  6. EventBus.getDefault().register(this);
  7. }
  8. @Override
  9. protected void onDestroy() {
  10. super.onDestroy();
  11. // ****** 不要忘了进行注销 ****
  12. EventBus.getDefault().unregister(this);
  13. }
  14. @Subcriber(tag = "csuicide")
  15. private void csuicideMyself(String msg) {
  16. finish();
  17. }
  18. }

代码5

代码5中,我们定义一个CsuicideActivity在onCreate中注册该Activity对象,在onDestroy中注销,还添加了一个csuicideMyself的订阅函数。所有的Activity类可以继承自CsuicideActivity。当需要退出应用时,直接发布一个类型为String、tag为csuicide的事件即可。这样所有的Activity就会触发csuicideMyself,而该函数中又调用了finish方法,因此所有的Activity都将退出,通过这种方式就完成了应用退出。

自定义事件处理器 ( EventHandler )

AndroidEventBus在设计之初就考虑到了可扩展性,主要可扩展的地方就是订阅函数的搜索策略,具体可以调用EventBus.getDefualt().setMatchPolicy(MatchPolicy policy)来实现策略替换。另一个比较重要的扩展就是事件处理器EventHandler,用户可以通过setter函数来设置三个事件处理器。如代码6所示。

  1. /**
  2. * 设置执行在UI线程的事件处理器
  3. * @param handler   UI线程事件处理器
  4. */
  5. public void setUIThreadEventHandler(EventHandler handler) {
  6. mDispatcher.mUIThreadEventHandler = handler;
  7. }
  8. /**
  9. * 设置执行在post线程的事件处理器
  10. * @param handler 事件在哪个线程投递,事件就执行在哪个线程的事件处理器
  11. */
  12. public void setPostThreadHandler(EventHandler handler) {
  13. mDispatcher.mPostThreadHandler = handler;
  14. }
  15. /**
  16. * 设置执行在异步线程的事件处理器
  17. * @param handler 异步线程事件处理器
  18. */
  19. public void setAsyncEventHandler(EventHandler handler) {
  20. mDispatcher.mAsyncEventHandler = handler;
  21. }

代码6

EventHandler的接口定义如代码7所示,只需实现handleEvent即可,然后将该实现注入到EventBus即可。

  1. /**
  2. * 事件处理接口,处理事件的抽象
  3. */
  4. public interface EventHandler {
  5. /**
  6. * 处理事件
  7. * @param subscription 订阅对象
  8. * @param event 待处理的事件
  9. */
  10. void handleEvent(Subscription subscription, Object event);
  11. }

代码7

默认有DefaultEventHandler、UIThreadEventHandler、AsyncEventHandler三个实现:

  • DefaultEventHandler:事件在哪个线程发布,就将事件接收函数执行在哪个线程;
  • UIThreadEventHandler:将事件接收函数执行在UI线程;
  • AsyncEventHandler:将事件接收函数执行在异步线程。

下面我们以自定义异步事件处理器,也就是AsyncEventHandler,通过实现EventHandler接口,将事件处理函数执行在一个线程池中,从而实现图片下载的功能。如代码8所示。

  1. public class ThreadPoolHandler implements EventHandler {
  2. ExecutorService mExecutorService = Executors.newFixedThreadPool(3);
  3. EventHandler mHandler = new DefaultEventHandler();
  4. @Override
  5. public void handleEvent(final Subscription subscription, final Object event) {
  6. mExecutorService.submit(new Runnable() {
  7. @Override
  8. public void run() {
  9. mHandler.handleEvent(subscription, event);
  10. }
  11. });
  12. }
  13. }

代码8

然后通过如下代码将ThreadPoolEventHandler注入到:

图2  图片下载中页面

图3  图片下载完成页面

EventBus中:

  1. // 自定义的异步事件处理器,使用线程池
  2. EventBus.getDefault().setAsyncEventHandler(new ThreadPoolHandler());

再在订阅对象中添加代码9所示的订阅方法 :

  1. @Subcriber(tag = "download", mode = ThreadMode.ASYNC)
  2. private void downloadImage(final String imageUrl) {
  3. HttpURLConnection urlConnection = null;
  4. try {
  5. final URL url = new URL(imageUrl);
  6. urlConnection = (HttpURLConnection) url.openConnection();
  7. final Bitmap bmp = BitmapFactory.decodeStream(urlConnection.getInputStream());
  8. // 将Bitmap投递给ImageView之类的工作
  9. } catch (IOException e) {
  10. } finally {
  11. if (urlConnection != null) {
  12. urlConnection.disconnect();
  13. }
  14. }
  15. }

代码9

最后,当需要下载图片时,通过post发布一个参数为String类型、tag为download的事件即可执行downloadImage函数,这个函数将执行在线程池中,我们的简易ImageLoader就这么实现了。图2、图3分别为图片下载中和图片下载完成的页面。

当然,由于AndroidEventBus的高度定制化,我们还可以通过AndroidEventBus来实现各种各样的功能,它到底还能怎么玩,我就不做过多的演示了,开发者可以充分发挥自己的聪明才智和想象力。

Android事件总线EventBus详解的更多相关文章

  1. Android事件传递机制详解及最新源码分析——ViewGroup篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在上一篇<Android事件传递机制详解及最新源码分析--View篇>中,详细讲解了View事件的传递机制,没掌握或者掌握不扎实的小伙伴 ...

  2. Android事件分发机制详解

    事件分发机制详解 一.基础知识介绍 1.经常用的事件有:MotionEvent.ACTION_DOWN,MotionEvent.ACTION_MOVE,MotionEvent.ACTION_UP等 2 ...

  3. Android事件分发机制详解(2)----分析ViewGruop的事件分发

    首先,我们需要 知道什么是ViewGroup,它和普通的View有什么区别? ViewGroup就是一组View的集合,它包含很多子View和ViewGroup,是Android 所有布局的父类或间接 ...

  4. Android事件分发机制详解(1)----探究View的事件分发

    探究View的事件分发 在Activity中,只有一个按钮,注册一个点击事件 [java] view plaincopy button.setOnClickListener(new OnClickLi ...

  5. android 事件分发机制详解(OnTouchListener,OnClick)

    昨天做东西做到触摸事件冲突,以前也经常碰到事件冲突,想到要研究一下Android的事件冲突机制,于是从昨天开始到今天整整一天时间都要了解这方面的知识,这才懂了安卓的触摸和点击事件的机制.探究如下: 首 ...

  6. Android事件传递机制详解及最新源码分析——View篇

    摘要: 版权声明:本文出自汪磊的博客,转载请务必注明出处. 对于安卓事件传递机制相信绝大部分开发者都听说过或者了解过,也是面试中最常问的问题之一.但是真正能从源码角度理解具体事件传递流程的相信并不多, ...

  7. Android事件传递机制详解及最新源码分析——Activity篇

    版权声明:本文出自汪磊的博客,转载请务必注明出处. 在前两篇我们共同探讨了事件传递机制<View篇>与<ViewGroup篇>,我们知道View触摸事件是ViewGroup传递 ...

  8. Android 的事件传递机制,详解

    Android 的事件传递机制,详解 前两天和一个朋友聊天的时候.然后说到事件传递机制.然后让我说的时候,忽然发现说的不是非常清楚,事实上Android 的事件传递机制也是知道一些,可是感觉自己知道的 ...

  9. Android开发——事件分发机制详解

    0. 前言   转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/52566965 深入学习事件分发机制,是为了解决在Android开发中 ...

随机推荐

  1. hihoCoder 1595 : Numbers

    Description You are given n constant integers c[1], c[2], ..., c[n] and an integer k. You are to ass ...

  2. ●洛谷P3348 [ZJOI2016]大森林

    题链: https://www.luogu.org/problemnew/show/P3348 题解: LCT,神题 首先有这么一个结论: 每次的1操作(改变生长点操作),一定只会会对连续的一段区间产 ...

  3. IO复用

    IO复用:使得程序能同时监听多个文件描述符 select: select在一段指定的时间内,监听用户感兴趣的文件描述符的 读.写.异常事件. select(int nfds,fd_set* readf ...

  4. Codeforces Round #438 C. Qualification Rounds

    Description Snark and Philip are preparing the problemset for the upcoming pre-qualification round f ...

  5. poj 3525 凸多边形多大内切圆

    Most Distant Point from the Sea Time Limit: 5000MS   Memory Limit: 65536K Total Submissions: 4758   ...

  6. [HEOI 2016] seq

    题解: 发现多决策且明显无后效性,果断dp,那么转移方程F[i]=F[j]+1 设R[I]为改变之后的最大值,L[i]为改变之后的最小值 由于只能改变一个元素 所以转移的条件是 (j<i &am ...

  7. C语言中#define的用法

    今天整理了一些#define的用法,与大家共享! 1.简单的define定义 #define MAXTIME 1000 一个简单的MAXTIME就定义好了,它代表1000,如果在程序里面写 if(i& ...

  8. MySql查询不区分大小写解决方案(两种)

    当我们输入不管大小写都能查询到数据,例如:输入 aaa 或者aaA ,AAA都能查询同样的结果,说明查询条件对大小写不敏感. 解决方案一: 于是怀疑Mysql的问题.做个实验:直接使用客户端用sql查 ...

  9. SCNN车道线检测--(SCNN)Spatial As Deep: Spatial CNN for Traffic Scene Understanding(论文解读)

    Spatial As Deep: Spatial CNN for Traffic Scene Understanding 收录:AAAI2018 (AAAI Conference on Artific ...

  10. web.xml is missing and <failOnMissingWebXml> is set to true

    这时候需要右击项目-->Java EE Tools-->Generate Deployment Descriptor Stub .然后系统会在src/main/webapp/WEB_INF ...