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

做定时任务对于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();

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

  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"这个库的实现原理。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,717评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,501评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,311评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,417评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,500评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,538评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,557评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,310评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,759评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,065评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,233评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,909评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,548评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,172评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,420评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,103评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,098评论 2 352

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,980评论 25 707
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,646评论 18 139
  • Android Handler机制系列文章整体内容如下: Android Handler机制1之ThreadAnd...
    隔壁老李头阅读 8,169评论 8 57
  • 我记得我开始使用轮询是在做直播项目的时候,当时是在直播间中用来做弹幕的功能的。 在android中可以实现轮询的方...
    Orz013阅读 5,408评论 0 7
  • 路上看了大概一半的《把时间当朋友》,整理一下书中提到的方法论。 成长只有一条路——积累 关于理性思维,书中提到: ...
    袁公子爱生活阅读 269评论 0 0