做定时任务对于android可以有好多种实现方式:
1. AlarmManager
利用系统的”闹钟”功能来做定时、心跳,这个服务的优点就是足够精确,同时根据设置不同type类型可以做到锁屏、甚至使用AlarmManager.POWER_OFF_WAKEUP关机的时候还保持心跳(这是真正利用了硬件的计时,一旦到达指定的任务执行时间就会唤醒CPU来执行,不过受限于一些SDK版本的影响,有些版本不支持),这里就不展开详细讲解,下面附上一个封装的小工具,源码:
/** * 项目名:不告诉你 * 包名: 不告诉你 * 文件名:${} * 作者: Jerry on 2016/12/7 15:42 * 邮箱:JerryloveEmily@163.com */
public class TimeTaskUtil {
private final AlarmManager mAlarManager;
public TimeTaskUtil(Context context) {
mAlarManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
/** * 开启轮询服务 * * @param context 上下文 * @param cls service服务对象 * @param requestCode 请求码 * @param intervalSecond 每隔几秒执行一次任务 * @param bundle 轮询服务传递的参数 */
public void startPollingService(Context context, Class<?> cls, String action, int requestCode, int intervalSecond, Bundle bundle) {
Intent intent = new Intent(context, cls);
intent.setAction(action);
intent.putExtra("bundle", bundle);
PendingIntent pi = PendingIntent.getService(
context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
mAlarManager.setRepeating(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime(), intervalSecond * 1000, pi);
}
/** * 停止轮询服务 * * @param context 上下文 * @param cls 轮询服务对象 * @param action 任务的action */
public void stopPollingService(Context context, Class<?> cls, int requestCode, String action) {
Intent intent = new Intent(context, cls);
intent.setAction(action);
PendingIntent operationIntent = PendingIntent.getService(
context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT
);
mAlarManager.cancel(operationIntent);
}
}
以上只是针对AlarmManager和Service之间的任务心跳处理,AlarmManager还可以和Activity、BroadcastReceiver等组件做类似的心跳回调处理,就不一一介绍了和以上工具类似。
2. JDK中的Timer
同样可以用来做轮询心跳,不详述,想必小伙伴们都很熟悉它。
3. 使用Handler来做轮询
进入正题,大家都知道Handler内部维护了一个消息循环队列,不断的处理着我们send过去的消息,我们就可以利用这个机制来实现心跳轮询的效果,不多说直接上源码:
/** * 项目名:Jerry * 包名: Jerry * 文件名:${PollingTask} * 作者: Jerry on 2016/12/10 09:45 * 邮箱:JerryloveEmily@163.com */
public class PollingTask {
private static final String TAG = "PollingTask";
private Handler mHandler;
private InnerTask mInnerTask;
private TaskObserver mTaskObserver;
// 默认心跳时间5秒
private static final int HEART_BEAT_RATE = 5;
private long mHeartBeatRate;
private TaskObservable mObservable;
/** * 创建任务 * @return ... */
public PollingTask createTask(){
createTask(HEART_BEAT_RATE);
return this;
}
/** * 创建任务 * @param heartBeatRate 心跳时间,单位秒 * @return ... */
public PollingTask createTask(int heartBeatRate) {
this.mHeartBeatRate = heartBeatRate * 1000;
mHandler = new Handler();
mInnerTask = new InnerTask(this);
mTaskObserver = new TaskObserver();
mObservable = new TaskObservable();
mObservable.addObserver(mTaskObserver);
return this;
}
/** * 设置心跳任务时间 * @param heartBeatRate 心跳时间,单位秒 * @return ... */
public PollingTask setHearBeatRate(int heartBeatRate){
this.mHeartBeatRate = heartBeatRate * 1000;
return this;
}
/** * 链接启动任务 * @return ... */
public PollingTask connected() {
if (mHandler == null || mInnerTask == null) {
throw new RuntimeException("please call createTask method create polling task!");
}
if (mHeartBeatRate <= 0) {
throw new RuntimeException("please set HeartBeatRate by setHearBeatRate method!");
}
mHandler.removeCallbacks(mInnerTask);
mHandler.post(mInnerTask);
return this;
}
/** * 通知任务执行完成 */
public void notifyTaskFinish(){
mObservable.notifyTaskFinish();
}
/** * 销毁任务 */
public void destroyTask(){
if (mHandler == null || mInnerTask == null) {
throw new RuntimeException("please call createTask method create polling task!");
}
mHandler.removeCallbacks(mInnerTask);
}
private class InnerTask implements Runnable {
private WeakReference<PollingTask> mWeak;
InnerTask(PollingTask wrapper) {
mWeak = new WeakReference<>(wrapper);
}
@Override
public void run() {
PollingTask outClass = mWeak.get();
if (outClass != null) {
if (outClass.mOnTaskListener != null) {
// 开始执行任务
outClass.mOnTaskListener.executeTask(outClass);
}
}
}
}
/** * 任务观察者 */
private class TaskObserver implements Observer {
@Override
public void update(Observable observable, Object data) {
// 通过观察者模式,被观察的任务通知了任务轮询观察者,需要去更新开启新的一轮任务的执行,利用handler的postDelayed起到延时心跳的效果
mHandler.postDelayed(mInnerTask, mHeartBeatRate);
}
}
/** * 被观察的任务 */
private class TaskObservable extends Observable {
public void notifyTaskFinish() {
// 标识状态或者内容发送改变
setChanged();
// 通知所有的观察者
notifyObservers();
}
}
private OnTaskListener mOnTaskListener;
/** * 监听任务状态 * @param listener 监听接口 */
public PollingTask setOnTaskListener(OnTaskListener listener) {
this.mOnTaskListener = listener;
return this;
}
public interface OnTaskListener {
void executeTask(PollingTask pollingTask);
}
}
- 使用这个库创建一个心跳任务很简单,而且是链式的调用方法:
// 初始化一个心跳任务对象
PollingTask mPushPollingTask = new PollingTask();
// 创建一个60秒为心跳的任务
mPushPollingTask.createTask(60)
.connected()
.setOnTaskListener(new PollingTask.OnTaskListener() {
@Override
public void executeTask(PollingTask pollingTask) {
// 执行一个任务,可以是同步的也可以是异步的
// 比如获取推送的新闻信息
getPushNewsInfos();
}
}
);
/** * 获取刷新规则的数据信息 * / private void getPushNewsInfos() { mApiWrapper.getPushNewsInfos(url, new ApiWrapper.HttpCallback<List<NewsInfo>>() { @Override public void success(List<NewsInfo> data) { savaNewsInfo(data); // 关键的一步,一旦一次任务执行完后,调用pollingTask的notifyTaskFinish方法,去通知任务观察者去更新再次发起一个任务的执行,否则轮询就无效,这个好比ListView、RecyclerView的Adatper里的数据刷新机制是一样的 mPushPollingTask .notifyTaskFinish(); } @Override public void failure(Exception e) { mPushPollingTask .notifyTaskFinish(); } }); } // 最后需要关闭回收一个心跳任务,只要调用 mPushPollingTask.destroyTask();
总结:主要有两个知识点:
- 使用handler的循环队列处理机制做到轮询心跳。
- 利用观察者模式,对具体任务的执行与PollingTask内部的Runnable实现类InnerTask之间的解耦,因为这样不管是什么任务都不要持有handler对象来做循环的任务执行工作。这里把每一个具体的任务看做是一个被观察的对象,一旦任务执行完成,就通知观察者mPollingTask .notifyTaskFinish()。这个机制有点像ListView、RecyclerView的Adatper里的数据刷新notifyDataSetChanged(),下次就来分析分析notifyDataSetChanged的实现原理和机制。
存在的问题:
- 一个心跳任务就得创建一个PollingTask对象不合适
- 一个PollingTask对象创建的心跳任务,同时只能处理一个异步任务,如果executeTask方法中执行一个以上的异步任务,就会出现mPollingTask .notifyTaskFinish()这个方法调用时机的问题,因为不同任务执行完成的时间是不一样的。
解决方案:
暂时还没有很明确的方法,简单的思路是重构PollingTask,内部维护一个任务和任务tag的map,同时映射对应一个观察者和被观察者的map,做遍历检查任务是否已经执行完成,同时通知到对应的任务观察者去更新心跳状态。可以参考”AndroidEventBus”这个事件总线库的思路,下次我们就来分析”AndroidEventBus”这个库的实现原理。