一个常见的需求是用户进入一个界面,请求一个接口获取数据,之后每隔固定时间请求一次接口刷新数据。当这个界面不再被显示时(比如用户按了 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 是安全的。