本文讨论 Android 应用如何实现轮询

一个常见的需求是用户进入一个界面,请求一个接口获取数据,之后每隔固定时间请求一次接口刷新数据。当这个界面不再被显示时(比如用户按了 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() 是我们需要每隔固定时间执行一次的方法。这样的实现存在若干问题:

我们可以把轮询的逻辑封装起来,并提供语义明确的声明式 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 是安全的。

comments powered by Disqus