顾名思义,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. TopCoder SRM 559 Div 1 - Problem 900 CircusTents

    传送门:https://284914869.github.io/AEoj/559.html 题目简述: n个实心圆,两两没有交集,在第一个圆上找一个点,使得它到另外一个圆上某个点的最短距离的最小值尽量 ...

  2. [Codeforces]852I - Dating

    题目大意:给定一棵n个点的树,每个点上有一个汉子或妹子,每人有一个权值,每次询问一条链上选出一对权值相等的男女有多少种选法.(n,q<=10^5) 做法:比较显然的树上莫队,熟悉序列莫队那套理论 ...

  3. AR8033 1000M模式下ping包丢包率过大分析与解决

    1 现象 近期对一款基于QCA方案.有线Phy为AR8033.WiFi双频且支持iEEE802.11AC的WLAN产品进行了深度验证,发现有线口同部分PC机直连时,WiFi终端ping 该PC机时总是 ...

  4. Python中str字符串的功能介绍

    Str字符串的功能介绍 1. 字符串的操作 字符串的连接操作 符号: + 格式:str1 + str2 例如:str1 = 'I Love' str2 = 'You!' print(str1 + st ...

  5. EF 6.x、EF Core实现dynamic动态查询和EF Core实现多个上下文实例池你了解多少?

    前言 很长一段时间没有写博客了,今天补上一篇吧,偶尔发现不太愿意写博客了,太耗费时间,不过还是在坚持当中,毕竟或许写出来的东西能帮到一些童鞋吧,接下来我们直奔主题.无论是在在EF 6.x还是EF Co ...

  6. python2.7练习小例子(二)

        2):题目:企业发放的奖金根据利润提成.利润(I)低于或等于10万元时,奖金可提10%:利润高于10万元,低于20万元时,低于10万元的部分按10%提成,高于10万元的部分,可提成7.5%:2 ...

  7. Docker学习笔记【三】安装Redis

    项目中使用到Redis,平常都是别人搭建的,今天试着在Google Cloud Platform 上搭建一个学习环境. 1.使用 docker pull redis 从docker hub中下载镜像 ...

  8. 如何在 vmware esxi 中开放 VNC功能及端口实现远程管理 完整篇

    VMWare esxi中开放 VNC功能及端口实现远程管理 完整篇 在多个论坛上看了相关文章,总的写得不完整.现将各方资源整编写完整版.详文如下! (图片来自51CTO) 步骤1. 修改ESXi主机的 ...

  9. IOI2016Day2. paint

    题目链接:http://uoj.ac/problem/238 题目大意: 有一个长度为n的黑白序列,告诉你所以k个极长连续黑段长度和顺序.有一些位置的颜色已知,需要判断剩下未知的位置哪些颜色 一定是白 ...

  10. 吴恩达深度学习第1课第4周-任意层人工神经网络(Artificial Neural Network,即ANN)(向量化)手写推导过程(我觉得已经很详细了)

    学习了吴恩达老师深度学习工程师第一门课,受益匪浅,尤其是吴老师所用的符号系统,准确且易区分. 遵循吴老师的符号系统,我对任意层神经网络模型进行了详细的推导,形成笔记. 有人说推导任意层MLP很容易,我 ...