PollingTask,一个简单的心跳轮询库

做定时任务对于android可以有好多种实现方式:

1. AlarmManager

利用系统的"闹钟"功能来做定时、心跳,这个服务的优点就是足够精确,同时根据设置不同type类型可以做到锁屏、甚至使用AlarmManager.POWER_OFF_WAKEUP关机的时候还保持心跳(这是真正利用了硬件的计时,一旦到达指定的任务执行时间就会唤醒CPU来执行,不过受限于一些SDK版本的影响,有些版本不支持),这里就不展开详细讲解,下面附上一个封装的小工具,源码:

/** 
* 项目名:不告诉你 
* 包名:  不告诉你 
* 文件名:${} 
* 作者:  Jerry on 2016/12/7 15:42 
* 邮箱:[email protected] 
*/
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
 * 邮箱:[email protected]
 */
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 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>() {
        @Override
        public void success(List data) {
            savaNewsInfo(data);
            // 关键的一步,一旦一次任务执行完后,调用pollingTask的notifyTaskFinish方法,去通知任务观察者去更新再次发起一个任务的执行,否则轮询就无效,这个好比ListView、RecyclerView的Adatper里的数据刷新机制是一样的
            mPushPollingTask .notifyTaskFinish();
        }
        @Override
        public void failure(Exception e) {
            mPushPollingTask .notifyTaskFinish();
        }
    });
}

// 最后需要关闭回收一个心跳任务,只要调用
mPushPollingTask.destroyTask();

总结:主要有两个知识点:

  1. 使用handler的循环队列处理机制做到轮询心跳。
  2. 利用观察者模式,对具体任务的执行与PollingTask内部的Runnable实现类InnerTask之间的解耦,因为这样不管是什么任务都不要持有handler对象来做循环的任务执行工作。这里把每一个具体的任务看做是一个被观察的对象,一旦任务执行完成,就通知观察者mPollingTask .notifyTaskFinish()。这个机制有点像ListView、RecyclerView的Adatper里的数据刷新notifyDataSetChanged(),下次就来分析分析notifyDataSetChanged的实现原理和机制。

存在的问题:

  1. 一个心跳任务就得创建一个PollingTask对象不合适
  2. 一个PollingTask对象创建的心跳任务,同时只能处理一个异步任务,如果executeTask方法中执行一个以上的异步任务,就会出现mPollingTask .notifyTaskFinish()这个方法调用时机的问题,因为不同任务执行完成的时间是不一样的。

解决方案:
暂时还没有很明确的方法,简单的思路是重构PollingTask,内部维护一个任务和任务tag的map,同时映射对应一个观察者和被观察者的map,做遍历检查任务是否已经执行完成,同时通知到对应的任务观察者去更新心跳状态。可以参考"AndroidEventBus"这个事件总线库的思路,下次我们就来分析"AndroidEventBus"这个库的实现原理。

你可能感兴趣的:(PollingTask,一个简单的心跳轮询库)