class="FocusMe">作者简介:何红辉,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 38class
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 26class
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简单概括只有三个步骤:
接下来就是注册订阅对象,如代码3所示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16public
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 17public
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添加了以下三个订阅函数:
从上述的描述中我们可以知道,事件接收函数主要有两个约束:事件类型和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
}
在上述代码中有一段代码是这样的:
1 2 3@Subcriber
(mode = ThreadMode.MAIN)
private
void
toastMsgFromEvent(String msg) {
}
这个mode可是大有来头,它指定这个事件接收函数执行在哪个线程中。具体有如下三个选项:
图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 18public
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三个实现:
下面我们以自定义异步事件处理器,也就是AsyncEventHandler,通过实现EventHandler接口,将事件处理函数执行在一个线程池中,从而实现图片下载的功能。如代码8所示。
1 2 3 4 5 6 7 8 9 10 11 12 13public
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来实现各种各样的功能,它到底还能怎么玩,我就不做过多的演示了,开发者可以充分发挥自己的聪明才智和想象力。