一个常见的需求是用户进入一个界面,请求一个接口获取数据,之后每隔固定时间请求一次接口刷新数据。当这个界面不再被显示时(比如用户按了 Home 键),停止轮询。看似很简单,5分钟可以搞定,像下面这样(下述代码位于一个有轮询需求的 Activity 内):
private Handler mHandler = new Handler();
private boolean isPolling;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do other initialization task
doSomething();
isPolling = true;
poll();
}
@Override
protected void onResume() {
super.onResume();
isPolling = true;
poll();
}
@Override
protected void onPause() {
super.onPause();
isPolling = false;
}
private void doSomething() {
// the task in need of polling
}
private void poll() {
if (isPolling) {
// or could we call doSomething() here?
mHandler.removeCallbacks(r);
mHandler.postDelayed(r, 5000);
}
}
private Runnable r = new Runnable() {
@Override
public void run() {
doSomething();
poll();
}
};
其中 doSomething() 是我们需要每隔固定时间执行一次的方法。这样的实现存在若干问题:
poll() 方法isPolling,增加维护状态的成本run() 方法里调用 doSomething(),也可以在 poll() 方法中调用;对 isPolling 的检查有可能放在两个方法的任意一个。团队不同成员之间很难统一。这会增加阅读理解代码的难度,并且埋下了产生 bug 的隐患。poll() 会立即执行 doSomething() 吗?连续多次调用 poll() 方法安全吗?不知道,只能去看代码实现细节。我们可以把轮询的逻辑封装起来,并提供语义明确的声明式 API
public class PollingDevice {
private Handler mHanlder;
private Map<Runnable, Runnable> mTaskMap = new HashMap<Runnable, Runnable>();
public PollingDevice(Handler handler) {
mHanlder = handler;
}
public void startPolling(Runnable runnable, long interval) {
startPolling(runnable, interval, false);
}
public void startPolling(final Runnable runnable, final long interval,
boolean runImmediately) {
if (runImmediately) {
runnable.run();
}
Runnable task = mTaskMap.get(runnable);
if (task == null) {
task = new Runnable() {
@Override
public void run() {
runnable.run();
post(runnable, interval);
}
};
mTaskMap.put(runnable, task);
}
post(runnable, interval);
}
public void endPolling(Runnable runnable) {
if (mTaskMap.containsKey(runnable)) {
mHanlder.removeCallbacks(mTaskMap.get(runnable));
}
}
private void post(Runnable runnable, long interval) {
Runnable task = mTaskMap.get(runnable);
mHanlder.removeCallbacks(task);
mHanlder.postDelayed(task, interval);
}
}
封装后实现轮询变得非常简单:
private PollingDevice mPollingDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do other initialization task
mPollingDevice = new PollingDevice(new Handler());
doSomething();
}
@Override
protected void onResume() {
super.onResume();
mPollingDevice.startPolling(r, 5000);
}
@Override
protected void onPause() {
super.onPause();
mPollingDevice.endPolling(r);
}
private void doSomething() {
// the task in need of polling
}
private Runnable r = new Runnable() {
@Override
public void run() {
doSomething();
}
};
可以看到,只需要在相关生命周期方法调用开始和停止轮询方法,并把需要执行的内容作为参数传进去,即可实现轮询。PollingDevice 保证重复调用轮询 API 是安全的。