TimothyQiu's Blog

keep it simple stupid

SwipeRefreshLayout 及 Otto 的多线程使用

分类:技术

前几周周末百无聊赖,决定利用周末开始一个开发 Android 微博客户端的漫长路程。主要是为了熟悉下 Android Studio 和「更 Android 的网络通信方式」。这周末由于公司项目的事情,没什么空搞这货,只给时间线加了一个下拉刷新的功能。

SwipeRefreshLayout

光是下拉刷新的话,其实用不着第三方库,安卓自带就有一个工具类 SwipeRefreshLayout,全名唤作 android.support.v4.widget.SwipeRefreshLayout,可以用作一个可滚动的视图(比如 ScrollViewListView)的容器,从而为这个视图提供下拉刷新功能。而且理论上根据系统的不同会有不同的表现形式,比如早期的就是 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 拉过头,就会触发 SwipeRefreshLayoutonRefresh 事件,并在界面上显示加载动画。

通过为 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 的 HandlerLooper 在 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