当一个Android应用功能越来越多的时候,保证应用的各个部分之间高效的通信将变得越来越困难。
在应用中的多个地方,控件经常需要根据某个状态来更新他们显示的内容。这种场景常见的解决方式就是定义一个接口,需要关注该事件的控件来实现这个接口。然后事件触发的地方来注册/取消注册这些对该事件感兴趣的控件。
例如,陌陌依赖手机位置信息来获取附近的用户,所以在位置更新管理器(MmLocationManager)中定义了一个接口来监听位置更新的事件(MmLocationListener):
interface MmLocationListener {
void onLocationChanged(Location location);
}
然后在应用的各个需要响应该事件的地方来实现上面的接口,然后在位置更新管理器(MmLocationManager)中注册/取消注册事件监听接口的实现类:
mLocationManager.get().register(this);
当不在需要监听的时候,取消注册:
mLocationManager.get().unregister(this);
问题
上面的解决方案是没问题的,但是不是理想方案。每个控件实现这个接口,导致这些控件和位置管理器注册强耦合在一起。这还意味着,当单元测试的时候,您需要模拟(mocked)位置管理器来生成位置更新事件。
随着应用功能的增加,需要监听的事件越来越多,而越来越多的控件需要监听不同的事件,则导致越来越多的控件需要注册到各种事件管理器上:
// 代码开始变得无法控制…
mLocationManager.get().register(this);
userAuthenticator.get().register(this);
settingsManager.get().register(this);
syncManager.get().register(this);
configurationMonitor.get().register(this);
注意:上面的每个事件的注册,都要实现对应的时间更新接口。
注册和取消注册这些事件慢慢的会变得越来越难以管理。导致测试越来越困难,并将导致开发者的效率越来越低,同时在您的应用中越来越容易引入各种奇怪的Bug。
解决方案
为了找出该问题的优雅解决方案,从一个意想不到的的地方借鉴点经验 — Swing应用。 Event Bus模式 — 也被称为Message Bus或者发布者/订阅者(publisher/subscriber)模式 — 可以让两个组件相互通信,但是他们之间并不相互知晓。
和需要注册各个事件的监听器相比,一个组件现在只用在Event Bus上注册一次即可:
bus.register(this);
上面的注册告诉Event Bus我们现在希望接收各个事件的更新。 然后Bus检测该类中每个带有@Subscribe注解的函数,当相关的事件发生的时候就调用这些带有注解的函数。
上面示例中的位置监听功能,不用实现位置监听接口和里面的函数了,只需要提供一个带有@Subscribe注解的函数即可:
@Subscribe
public void locationChanged(LocationChangedEvent event) {
// TODO React to location change.
}
现在Event Bus会把所有的LocationChangedEvent 事件都发送给上面的函数。MmLocationManager 类不用注册监听器了,当位置改变的时候 只需要向Event Bus发布事件即可:
bus.post(new LocationChangedEvent(37.892818, -121.772608));
注意:您也许已经发现该模式在Android上层也存在 — Intent系统就是这样设计的!
Android系统的Event Bus模式类库
1、Otto — Android系统的Event Bus类库
Otto是Square公司在他们应用中使用的Event Bus实现。从Guava中演变而来,并且专注于Android平台。
通过使用Otto,Square公司的应用组件间不紧密耦合了,单元测试也更加容易了。
Otto项目的主页:http://square.github.io/otto/
Otto项目GitHub地址:https://github.com/square/otto
2、EventBus — Android系统的Event Bus类库
EventBus 是http://greenrobot.de 出品的另外一个Event Bus类库,功能稍微多一点。
EventBus 项目地址:https://github.com/greenrobot/EventBus
关于两者的对比可以参考EventBus 项目Comparison with Square's Otto部分: https://github.com/greenrobot/EventBus#comparison-with-squares-otto
EventBus介绍
EventBus可以向不同的线程中发布事件,在ThreadMode 枚举中定义了4个线程,只需要在事件响应函数名称“onEvent”后面添加对应的线程类型名称,则还事件响应函数就会在对应的线程中执行,比如事件函数“onEventAsync”就会在另外一个异步线程中执行,ThreadMode定义的4个线程类型如下:
PostThread:事件响应函数和事件发布在同一线程中执行。这个是默认值,这样可以避免线程切换。
MainThread:事件响应函数会在Android应用的主线程(大部分情况下都是UI线程)中执行。
BackgroundThread:事件响应函数会在一个后台线程中执行。如果事件发布函数不是在主线程中,则会立即在事件发布线程中执行响应函数。如果事件发布函数在主线程中,EventBus则会在唯一的一个后台线程中按照顺序来执行所有的后台事件响应函数。
上面的3种事件响应函数,应该能够很快的执行完,不然的话会阻塞各自的事件发布。
async:事件响应函数在另外一个异步线程中执行。该线程和发布线程、主线程相互独立。如果事件响应函数需要较长的时间来执行,则应该使用该模式,例如 网络访问等。需要注意的是,由于系统并行的限制,应该避免在同一时间触发大量的异步线程。
在Android中我们可以在onCreate方法中调用EventBus的register(Object subscriber) 注册订阅者:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findView();
EventBus.getDefault().register(this);
}
调用 EventBus的unregister(Object subscriber) 方法 取消注册的订阅者:
@Override
protected void onDestroy() {
super.onDestroy();
try {
EventBus.getDefault().unregister(this);
} catch (Throwable t){
//this may crash if registration did not go through. just be safe
}
}
调用EventBus的post(Object event) 方法 post消息到Bus上,本例是在一个子线程中post一个消息到UI线程:
@Override
public void onRun() throws Throwable {
// TODO Auto-generated method stub
Log.e(TAG, "onRun sleep 6s");
Thread.sleep(6000);
EventBus.getDefault().post(new PostedCityEvent("posted"));
}
在订阅者中实现的 EventBus 上的事件处理方法,本例是在Activity中进行处理的:
public void onEventMainThread(PostedCityEvent ignored) {
Log.i(TAG, "PostedCityEvent status="+ignored.getStatus());
}
注意事件处理方法命名都是 onEventxxx,其中xxx表示 线程类型,如本例中线程类型是MainThread。
具体使用例子可以参考:http://awalkingcity.com/blog/2013/02/26/productive-android-eventbus/