SwipeRefreshLayout 及 Otto 的多线程使用
分类:技术
前几周周末百无聊赖,决定利用周末开始一个开发 Android 微博客户端的漫长路程。主要是为了熟悉下 Android Studio 和「更 Android 的网络通信方式」。这周末由于公司项目的事情,没什么空搞这货,只给时间线加了一个下拉刷新的功能。
SwipeRefreshLayout
光是下拉刷新的话,其实用不着第三方库,安卓自带就有一个工具类 SwipeRefreshLayout
,全名唤作 android.support.v4.widget.SwipeRefreshLayout
,可以用作一个可滚动的视图(比如 ScrollView
、ListView
)的容器,从而为这个视图提供下拉刷新功能。而且理论上根据系统的不同会有不同的表现形式,比如早期的就是 ActionBar 下边缘的横线动画,Material Design 后则是下拉的小圆形动画。
首先在 Gradle 中需要加入对 Support Library v4 的引用:
compile 'com.android.support:support-v4:21.0.0'
当然如果你的工程中已经间接引用了的话也可以不写,比如 v7 就依赖于 v4。
顾名思义,SwipeRefreshLayout
是一种 Layout,可以直接在 XML 文件里用,比如:
<android.support.v4.widget.SwipeRefreshLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/swipe_layout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ListView
android:id="@+id/listview_timeline"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.v4.widget.SwipeRefreshLayout>
此时,如果把 listview_timeline
拉过头,就会触发 SwipeRefreshLayout
的 onRefresh
事件,并在界面上显示加载动画。
通过为 SwipeRefreshLayout
设置 setOnRefreshListener
,即可监听刷新事件,重新加载数据。加载完成后,调用 setRefreshing(false)
即可终止刷新动画。
当然写完你就会发现这个刷新图标默认是纯黑色箭头,和我们在 Google+ 以及最近更新的 Chrome 里看到的画风不一样啊。实际上用 setColorSchemeResources
方法就可以为这个动画设置一系列颜色了。
Otto 的跨线程调用
因为我的客户端是在 Service
中请求时间线的,即在 Activity 中触发下拉刷新后,是直接开个新的 Service
去做网络请求。如果要跨组件传递「刷新完成」这样的消息,作为一个新手的我感觉会非常复杂。于是就想起来用 Otto。
Otto 是 Square 公司开源的一个事件总线库(Event Bus)。顺带说一句 Square 公司(不是 Enix 的那个 Square ;))贡献了很多优质精巧的 Android 开源库,比如 Picasso 和 Retrofit。
用起来大致就是先全局定一个 Bus
对象,比如写一个单例 BusProvider
;再为每个事件定义一个类,比如 TimelineUpdatedEvent
(空类即可);最后就可以在事件生成处——比如 Service
里——写:
BusProvider.getInstance().post(new TimelineUpdatedEvent());
而在事件接收处——比如 Activity
里——写:
@Override
public void onPause() {
super.onPause();
BusProvider.getInstance().unregister(this);
}
@Override
public void onResume() {
super.onResume();
BusProvider.getInstance().register(this);
}
@Subscribe
public void onTimelineUpdated(TimelineUpdatedEvent event) {
// 时间线已更新,停止刷新动画
}
然而光这样做却有一个问题:Otto 为了保持简洁,要求所有 post
发生在主线程中,而在 Service
中调用显然不在主线程,从而会导致抛出异常。所幸我们可以用 Android 的 Handler
和 Looper
在 Otto 默认的 Bus
周围再封装一层:
public class MainThreadBus extends Bus {
private final Handler handler = new Handler(Looper.getMainLooper());
@Override
public void post(final Object event) {
if (Looper.myLooper() == Looper.getMainLooper()) {
super.post(event);
} else {
handler.post(new Runnable() {
@Override
public void run() {
MainThreadBus.super.post(event);
}
});
}
}
}
思路很简单,post
前先检查自己是否在主线程,如果不是,让关联到主线程的 Handler
去调用原来的 post
。