Android事件总线还能怎么玩?
作者简介:何红辉,Android工程师,现任职于友盟。
顾名思义,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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
class ActivityA extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // ActivityA中注册广播接收器 registerReceiver( new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { User person = intent.getParcelableExtra( "user" ) ; } }, new IntentFilter( "my_action" )) ; } // ...... } // ActivityB中发布广播 class ActivityB extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // 发布广播 Intent intent = new Intent( "my_ action" ); intent.putExtra( "user" , new User( "mr.simple" )) ; sendBroadcast(intent); } // ...... } // 实体类需要实现序列化 class User implements Parcelable { String name ; public User(String aName) { name = aName ; } // 代码省略 @Override public void writeToParcel(Parcel dest, int flags) { dest.writeString(name); } } |
是不是有很麻烦的感觉?我们再来看一个示例,在开发过程中,我们经常要在子线程中做一些耗时操作,然后将结果更新到UI线程,除了AsyncTask之外,Thread加Handler是我们经常用的手段。如代码2所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
class MyActivity extends Activity { Handler mHandler = new Handler () { public void handleMessage(android.os.Message msg) { if ( msg.what == 1 ) { User user = (User)msg.obj ; // do sth } }; } ; @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // code ...... new Thread( new Runnable() { public void run() { // do sth User newUser = new User( "simple" ) ; Message msg = mHandler.obtainMessage() ; msg.what = 1 ; msg.obj = newUser ; mHandler.sendMessage(msg) ; } }).start(); } } |
是不是依然相当麻烦?当然你也可以使用AsyncTask来简化操作,但AsyncTask的几个泛型参数让你的代码看起来并不那么简洁,因此GitHub上出现了TinyTask、SimpleTask这些开源库来简化AsyncTask的使用。而这些,使用AndroidEventBus都可以很好地解决!
下面就让我们来领悟一下AndroidEventBus的强大魅力吧。
初见AndroidEventBus
使用AndroidEventBus简单概括只有三个步骤:
- 将对象注册到AndroidEventBus中;
- 使用@Subcriber标注订阅函数(只能有一个参数);
- 通过post函数发布事件。
接下来就是注册订阅对象,如代码3所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public class MainActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 将对象注册到事件总线中, ****** 注意要在onDestory中进行注销 **** EventBus.getDefault().register( this ); } @Override protected void onDestroy() { super .onDestroy(); // ****** 不要忘了进行注销 **** EventBus.getDefault().unregister( this ); } // 代码省略 } |
在onCreate中注册之后,MainActivity就可以添加订阅函数来接收消息了。需要注意的是在onDestory中需要将MainActivity从事件总线中注销。通过AndroidEventBus你可以去除Activity、Fragment、Service等组件的回调,减少了耦合,简化了代码。
事件订阅函数
事件订阅需要使用@Subscriber注解进行标识,且订阅函数的参数必须为一个。事件总线凭借参数类型和@Subscriber注解的tag值来标识订阅函数的唯一性。当用户发布事件时,总线库会根据事件类型和tag来查找符合要求的订阅函数,并且将这些订阅函数执行在对应的线程中。我们先来看看代码4的订阅函数示例。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
public class MainActivity extends Activity { // 代码省略 @Subcriber (tag = "csuicide" ) private void csuicideMyself(String msg) { // do sth finish(); } @Subcriber (mode = ThreadMode.MAIN) private void toastMsgFromEvent(String msg) { // do sth } @Subcriber (tag = "async" , mode = ThreadMode.ASYNC) private void executeAsync( final String msg) { // do sth } // 代码省略 } |
在代码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
2
3
4
|
@Subcriber (tag = "async" , mode = ThreadMode.ASYNC) private void executeAsync( final String msg) { // do sth } |
AndroidEventBus的ThreadMode
在上述代码中有一段代码是这样的:
1
2
3
|
@Subcriber (mode = ThreadMode.MAIN) private void toastMsgFromEvent(String msg) { } |
这个mode可是大有来头,它指定这个事件接收函数执行在哪个线程中。具体有如下三个选项:
- ThreadMode.MAIN,事件接收函数执行在UI线程;
- ThreadMode.POST,事件在哪个线程发布,接收函数就执行在哪个线程;
- ThreadMode.ASYNC,事件执行在一个独立的异步线程中。
图1中,事件接收函数就执行在异步线程。通过这几个线程模型,我们就可以定制接收函数的执行线程。这样我们就可以使用AndroidEventBus做很多事了。比如发布一个事件,在这个事件接收函数中进行耗时操作;或下载图片、进行HTTP请求、I/O操作等,以及替换Thread、AsyncTask等组件。不过,AndroidEventBus的功能远不止于此,下面我们就看看如何进行更高端的操作。
还可以怎么玩?
退出应用的另类实现
在Android应用开发中,有些情况下我们需要可以直接退出程序。但问题是,回退栈中含有其他的Activity存在,直接使用返回键并不能退出应用。此时我们常见的做法是再自定义一个Application子类,在子类中维护一个Activity的列表,然后在进入Activity时,将Activity添加到列表中,在Activity销毁之前将自己从Application子类的列表中移除。在需要退出应用时遍历Application子类的Activity列表,然后调用每个Activity的finish函数。那我们看看AndroidEventBus怎么实现这个功能。如代码5所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public class CsuicideActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super .onCreate(savedInstanceState); // 将对象注册到事件总线中, ****** 注意要在onDestory中进行注销 **** EventBus.getDefault().register( this ); } @Override protected void onDestroy() { super .onDestroy(); // ****** 不要忘了进行注销 **** EventBus.getDefault().unregister( this ); } @Subcriber (tag = "csuicide" ) private void csuicideMyself(String msg) { finish(); } } |
代码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
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
/** * 设置执行在UI线程的事件处理器 * @param handler UI线程事件处理器 */ public void setUIThreadEventHandler(EventHandler handler) { mDispatcher.mUIThreadEventHandler = handler; } /** * 设置执行在post线程的事件处理器 * @param handler 事件在哪个线程投递,事件就执行在哪个线程的事件处理器 */ public void setPostThreadHandler(EventHandler handler) { mDispatcher.mPostThreadHandler = handler; } /** * 设置执行在异步线程的事件处理器 * @param handler 异步线程事件处理器 */ public void setAsyncEventHandler(EventHandler handler) { mDispatcher.mAsyncEventHandler = handler; } |
EventHandler的接口定义如代码7所示,只需实现handleEvent即可,然后将该实现注入到EventBus即可。
1
2
3
4
5
6
7
8
9
10
11
|
/** * 事件处理接口,处理事件的抽象 */ public interface EventHandler { /** * 处理事件 * @param subscription 订阅对象 * @param event 待处理的事件 */ void handleEvent(Subscription subscription, Object event); } |
默认有DefaultEventHandler、UIThreadEventHandler、AsyncEventHandler三个实现:
- DefaultEventHandler:事件在哪个线程发布,就将事件接收函数执行在哪个线程;
- UIThreadEventHandler:将事件接收函数执行在UI线程;
- AsyncEventHandler:将事件接收函数执行在异步线程。
下面我们以自定义异步事件处理器,也就是AsyncEventHandler,通过实现EventHandler接口,将事件处理函数执行在一个线程池中,从而实现图片下载的功能。如代码8所示。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
public class ThreadPoolHandler implements EventHandler { ExecutorService mExecutorService = Executors.newFixedThreadPool( 3 ); EventHandler mHandler = new DefaultEventHandler(); @Override public void handleEvent( final Subscription subscription, final Object event) { mExecutorService.submit( new Runnable() { @Override public void run() { mHandler.handleEvent(subscription, event); } }); } } |
然后通过如下代码将ThreadPoolEventHandler注入到:
EventBus中:
1
2
|
// 自定义的异步事件处理器,使用线程池 EventBus.getDefault().setAsyncEventHandler( new ThreadPoolHandler()); |
再在订阅对象中添加代码9所示的订阅方法 :
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
@Subcriber (tag = "download" , mode = ThreadMode.ASYNC) private void downloadImage( final String imageUrl) { HttpURLConnection urlConnection = null ; try { final URL url = new URL(imageUrl); urlConnection = (HttpURLConnection) url.openConnection(); final Bitmap bmp = BitmapFactory.decodeStream(urlConnection.getInputStream()); // 将Bitmap投递给ImageView之类的工作 } catch (IOException e) { } finally { if (urlConnection != null ) { urlConnection.disconnect(); } } } |
最后,当需要下载图片时,通过post发布一个参数为String类型、tag为download的事件即可执行downloadImage函数,这个函数将执行在线程池中,我们的简易ImageLoader就这么实现了。图2、图3分别为图片下载中和图片下载完成的页面。
当然,由于AndroidEventBus的高度定制化,我们还可以通过AndroidEventBus来实现各种各样的功能,它到底还能怎么玩,我就不做过多的演示了,开发者可以充分发挥自己的聪明才智和想象力。
Android事件总线还能怎么玩?的更多相关文章
- Android事件总线分发库EventBus3.0的简单讲解与实践
Android事件总线分发库EventBus的简单讲解与实践 导语,EventBus大家应该不陌生,EventBus是一款针对Android优化的发布/订阅事件总线.主要功能是替代Intent,Han ...
- Android事件总线EventBus详解
顾名思义,AndroidEventBus是一个Android平台的事件总线框架,它简化了Activity.Fragment.Service等组件之间的交互,很大程度上降低了它们之间的耦合,使我们的代码 ...
- Android事件总线(一)EventBus3.0用法全解析
前言 EventBus是一款针对Android优化的发布/订阅事件总线.简化了应用程序内各组件间.组件与后台线程间的通信.优点是开销小,代码更优雅,以及将发送者和接收者解耦.如果Activity和Ac ...
- Android事件总线
Android中Activity.Service.Fragment之间的相互通信比较麻烦,主要有以下一些方法: (1)使用广播,发送者发出广播,接收者接收广播后进行处理: (2)使用Handler和M ...
- 【第三篇】学习 android 事件总线androidEventbus之发布事件,子线程中接收
发送和接收消息的方式类似其他的发送和接收消息的事件总线一样,不同的点或者应该注意的地方: 1,比如在子线程构造方法里面进行实现总线的注册操作: 2,要想子线程中接收消息的功能执行,必须启动线程. 3, ...
- 【第三篇】学习 android 事件总线androidEventbus之list数据事件的传递,发送list数据事件到另外一个Activity
这个和普通的事件总线的发送接收一样. package com.example.mysimpleeventbus; import java.util.ArrayList; import java.uti ...
- 【第二篇】学习 android 事件总线androidEventbus之异步事件的传递
1,不同Activity直接发送Ansy的事件,以及其他任何事件,必须通过 postSticky方式来进行事件的传递,而不能通过post的形式来进行传递:EventBus.getDefault().p ...
- 【第一篇】学习 android 事件总线androidEventbus之sticky事件的传递
最近再看eventbus相关代码,首先从使用开始,后期再从源码角度分析eventbus.使用Demo后期公布到github上去. 使用的框架地址:https://github.com/bboyfeiy ...
- 45、Android事件总线分发库的使用
事件总线分发库EventBus和Otto的简介及对比 什么是事件总线管理: a.将事件放到队列里,用于管理和分发b.保证应用的各个部分之间高效的通信及数据.事件分发c.模块间解耦 Event Bus是 ...
随机推荐
- Kafka:ZK+Kafka+Spark Streaming集群环境搭建(二十五)Structured Streaming:同一个topic中包含一组数据的多个部分,按照key它们拼接为一条记录(以及遇到的问题)。
需求: 目前kafka的topic上有一批数据,这些数据被分配到9个不同的partition中(就是发布时key:{m1,m2,m3,m4...m9},value:{records items}),m ...
- capwap学习笔记——初识capwap(四)
2.5.7 CAPWAP传输机制 WTP和AC之间使用标准的UDP客户端/服务器模式来建立通讯. CAPWAP协议支持UDP和UDP-Lite [RFC3828]. ¢ 在IPv4上,CAPWAP控制 ...
- (转)【风宇冲】Unity3D教程宝典之AssetBundles:第二讲
原创文章如需转载请注明:转载自风宇冲Unity3D教程学院 AssetBundles第二讲:AssetBundles与脚本 所有Unity的As ...
- Sublime 3156 LICENSE key
mac sublime3 3156 LICENSE 转自:http://blog.csdn.net/myboyliu2007/article/details/78748253 下载地址:https:/ ...
- 来自Vexels的超棒免费商业相关图标资源 #精选设计素材图标
如果你也在寻找一些商务相关的图标的话,今天推荐的这套图标资源绝对是你梦寐以求滴 , 首先呢请预览一下效果吧~ 这套来自Vexels的图标包含了28张商务和商业相关的内容,包含了战略,市场,项目,付款等 ...
- ArcMap工具箱参数名称的Bug
已经忍了很久了,今天一定要说一说,强大的 ArcGIS居然还存在这种Bug问题.如下图所示:使用了追加工具,有三个要素图层,且三个数据与目标数据不是同一数据,但它们的名称一致,这样执行,将会出现&qu ...
- 解决IE兼容模式的方案
在html头 加标签 强制使用最新的ie渲染 <meta http-equiv="X-UA-Compatible" content="IE=edge"&g ...
- SQL字符串分割解析
常用以下三种: [1]substring( expression ,start , length ): [2]CHARINDEX ( expression1 , expression2 [ , sta ...
- js - object.assign 以及浅、深拷贝
浅(引用)拷贝:共用同一内存地址,你改值我也变 譬如常用的对象赋值操作 深拷贝:深拷贝即创建新的内存地址保存值(互不影响) 譬如以下 const shallBasicCopy = obj => ...
- uni-app - 支付(app支付、小程序支付、h5(微信端)支付)
App支付.小程序支付.h5(微信端)支付 APP支付(内置) appPay.js /** * 5+App支付,仅支持支付宝以及微信支付 * * 支付宝Sdk集成,微信sdk未集成 * * @para ...