1. 程式人生 > >AlarmManager實現建立多個定時任務功能

AlarmManager實現建立多個定時任務功能

需求情景:

 接收伺服器下發的定時任務:定時亮屏、定時調節音量、定時調節亮度。

具體定時任務:

音量和亮度只需要在開始時間去執行,電源開關(其實是系統開關屏)需要在開始時間開啟,結束時間關閉。

具體調節亮度、音量、開關屏操作暫不講述。

1.獲取需要執行的定時任務 

 該類是我的資料庫操作類,包含了執行定時任務的操作,程式碼較長,覺得麻煩的同學可以先跳過,下面對這裡面的方法進行詳細講解。大概講下主要功能:通過呼叫該類的calculate方法儲存接收到的定時任務到資料庫,儲存之後會呼叫executeTimingPlanHandle方法去執行定時任務。 

package com.hx.station.model.ServicerModel;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.text.TextUtils;

import com.hx.station.application.Application;
import com.hx.station.bean.TimingPlanBean;
import com.hx.station.dao.TimingBeanDao;
import com.hx.station.entity.TimingBean;
import com.hx.station.help.BaseDbHelper;
import com.hx.station.model.CalculatorStrategy;
import com.hx.station.presenter.DetectionPresenter;
import com.hx.station.receiver.TimingPlanReceiver;
import com.hx.station.utils.AlarmManagerUtils;
import com.hx.station.utils.GsonUtils;
import com.hx.station.utils.TimeUtils;

import java.util.ArrayList;
import java.util.List;

import static android.app.AlarmManager.INTERVAL_DAY;
import static com.hx.station.listener.CommandNum.CMD_DEVICE_BRIGHTNESS;
import static com.hx.station.listener.CommandNum.CMD_DEVICE_POWER;
import static com.hx.station.listener.CommandNum.CMD_DEVICE_VOLUME;

/**
 * 使用策略模式,對伺服器下發的定時方案進行解析並儲存
 *
 * @author bicycle
 *
 * @version 1.0.2
 *
 * @since 2018年11月01日
 */
public class TimingPlanHandler extends BaseDbHelper<TimingBean,String> implements CalculatorStrategy {

    private Activity mActivity;
    private static TimingPlanHandler timingPlanHandler;

    public static TimingPlanHandler getInstance(Activity activity) {
        if (timingPlanHandler == null) {
            synchronized (TimingPlanHandler.class) {
                if (timingPlanHandler == null) {
                    timingPlanHandler = new TimingPlanHandler(activity);
                }
            }
        }
        return timingPlanHandler;
    }

    private TimingPlanHandler(Activity activity){
        super(Application.daoSession.getTimingBeanDao());
        this.mActivity = activity;
    }

    /**
     * 儲存資料到庫,並根據資料執行定時
     *
     * @param content   伺服器返回得資料
     *
     * @return
     */
    @Override
    public String calculate(String content) {
        if (TextUtils.isEmpty(content)){
            return null;
        }
        TimingPlanBean timingJson = GsonUtils.jsonToModule(content,TimingPlanBean.class);
        List<TimingBean> timingBeanList = new ArrayList<>();
        List<TimingPlanBean.DataBean.DeviceTimingsBean> beans = timingJson.getData().getDeviceTimings();
        for (int i = 0; i < beans.size(); i++) {
            TimingBean bean = new TimingBean();
            bean.setTimingId(beans.get(i).getId());
            bean.setSn(beans.get(i).getSn());
            bean.setType(beans.get(i).getType());
            bean.setStart(beans.get(i).getStart());
            bean.setEnd(beans.get(i).getEnd());
            bean.setParam(beans.get(i).getParam());
            timingBeanList.add(bean);
        }
        insertTiming(timingBeanList);
        executeTimingPlanHandle(timingBeanList);
        return null;
    }

    /**
     * 執行定時方案
     *
     * @param timingBeans   定時方案資料
     */
    @SuppressLint("WrongConstant")
    private void executeTimingPlanHandle(List<TimingBean> timingBeans){
        if (timingBeans.size() < 1) {
            return;
        }
        AlarmManagerUtils.getInstance(mActivity).alarCancel();
        for (int i = 0; i < timingBeans.size(); i++){
            //現在處於哪個執行階段
            int isExecute = TimeUtils.isTime(timingBeans.get(i).getStart(),timingBeans.get(i).getEnd());
            switch (timingBeans.get(i).getType()){
                case CMD_DEVICE_VOLUME:
                    //取出定時方案的時間進行判斷
                    //1現在時間在執行時間中間,立即執行一次定時,並開啟一個間隔一天執行一次得定時任務
                    //2現在時間還沒到執行時間,計算到開始時間還差多久,並開啟一個間隔一天執行一次得定時任務
                    //3現在時間已經過了執行時間,計算距離明天開始執行還差多久,並開啟一個間隔一天執行一次得定時任務
                    if (isExecute == TimeUtils.started) {
                        //現在時間在執行時間段中間
                        if(Build.VERSION.SDK_INT < 19){
                            //立即執行一次
                            AlarmManagerUtils.getInstance(mActivity).set(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate);
                            //第二天到了開始時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate,0);
                        }
                        //還沒到執行時間
                    } else if (isExecute == TimeUtils.notStart) {
                        if(Build.VERSION.SDK_INT < 19){
                            //到了執行時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                        }
                    } else {
                        //已過了執行時間
                        if(Build.VERSION.SDK_INT < 19){
                            //到了第二天開始時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                        }
                    }
                    break;
                case CMD_DEVICE_BRIGHTNESS:
                    if (isExecute == TimeUtils.started) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).set(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate,0);
                        }
                    }else if (isExecute == TimeUtils.notStart){
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                        }
                    }else {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                        }
                    }
                    break;
                case CMD_DEVICE_POWER:
                    if (isExecute == TimeUtils.started) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).set("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i+98,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate,0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),0);
                        }
                    } else if (isExecute == TimeUtils.notStart) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),0);
                        }
                    } else {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToEndTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToEndTimeMills(),0);
                        }
                    }
                    break;
            }
        }
    }

    /**
     * 程式啟動後讀取定時任務表,不為空就去執行
     */
    public void startExecutionTimingPlan(){
        if (null != findTiming()){
            executeTimingPlanHandle(findTiming());
        }
    }

    /**
     * 更新定時;清空表後再儲存
     *
     * @param timingBeans   需要儲存的資料
     */
    private void insertTiming(List<TimingBean> timingBeans){
        deleteAll();
        save(timingBeans);
    }

    /**
     * 查詢所有資料
     *
     * @return  查詢結果
     */
    private List<TimingBean> findTiming(){
        return queryBuilder().count() == 0 ? null:queryBuilder().list();
    }

    /**
     * 根據id查詢資料
     *
     * @param id    資料id
     *
     * @return  查詢結果
     */
    public List<TimingBean> findTimingById(Long id){
        return queryBuilder().where(TimingBeanDao.Properties.Id.eq(id)).count() == 0 ? null:queryBuilder().where(TimingBeanDao.Properties.Id.eq(id)).list();
    }

}

2.執行定時方案

這裡單獨講下executeTimingPlanHandle方法,這個方法是我執行定時任務的方法。首先我需要定時的任務可能是會在不同時間段進行的,所以我首要先對定時任務的開始執行時間、結束時間跟現在時間做對比,判斷得出是應該立馬執行還是等待多久執行。比如我現在時間是8:00,定時方案分別是0:00-2:00、7:00-9:00、10:00-12:00。那麼這三個定時分別是過了執行時間、立馬執行(現在時間在定時方案的中間)、未到執行時間。

/**
     * 執行定時方案
     *
     * @param timingBeans   需要執行的定時任務
     */
    @SuppressLint("WrongConstant")
    private void executeTimingPlanHandle(List<TimingBean> timingBeans){
        if (timingBeans.size() < 1) {
            return;
        }
        //在執行之前清空之前的定時任務
        AlarmManagerUtils.getInstance(mActivity).alarCancel();
        遍歷執行所有定時任務
        for (int i = 0; i < timingBeans.size(); i++){
            //現在處於哪個執行階段
            int isExecute = TimeUtils.isTime(timingBeans.get(i).getStart(),timingBeans.get(i).getEnd());
            switch (timingBeans.get(i).getType()){
                //調節音量
                case CMD_DEVICE_VOLUME:
                    //取出定時方案的時間進行判斷
                    //1現在時間在執行時間中間,立即執行一次定時,並開啟一個間隔一天執行一次得定時任務
                    //2現在時間還沒到執行時間,計算到開始時間還差多久,並開啟一個間隔一天執行一次得定時任務
                    //3現在時間已經過了執行時間,計算距離明天開始執行還差多久,並開啟一個間隔一天執行一次得定時任務
                    if (isExecute == TimeUtils.started) {
                        //現在時間在執行時間段中間
                        if(Build.VERSION.SDK_INT < 19){
                            //立即執行一次
                            AlarmManagerUtils.getInstance(mActivity).set(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate);
                            //第二天到了開始時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate,0);
                        }
                        //還沒到執行時間
                    } else if (isExecute == TimeUtils.notStart) {
                        if(Build.VERSION.SDK_INT < 19){
                            //到了執行時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                        }
                    } else {
                        //已過了執行時間
                        if(Build.VERSION.SDK_INT < 19){
                            //到了第二天開始時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                        }
                    }
                    break;
                //調節亮度
                case CMD_DEVICE_BRIGHTNESS:
                    if (isExecute == TimeUtils.started) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).set(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate,0);
                        }
                    }else if (isExecute == TimeUtils.notStart){
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                        }
                    }else {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                        }
                    }
                    break;
                //開關屏
                case CMD_DEVICE_POWER:
                    if (isExecute == TimeUtils.started) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).set("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i+98,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate,0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),0);
                        }
                    } else if (isExecute == TimeUtils.notStart) {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+TimeUtils.startToNowTimeMillis(),0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+TimeUtils.endToNowTimeMillis(),0);
                        }
                    } else {
                        if(Build.VERSION.SDK_INT < 19){
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("on",CMD_DEVICE_POWER,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                            AlarmManagerUtils.getInstance(mActivity).setRepeating("off",CMD_DEVICE_POWER,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToEndTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow("on",CMD_DEVICE_BRIGHTNESS,i,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),0);
                            AlarmManagerUtils.getInstance(mActivity).setWindow("off",CMD_DEVICE_BRIGHTNESS,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToEndTimeMills(),0);
                        }
                    }
                    break;
            }
        }
    }

首先我把所有的定時任務都獲取儲存到list種,再遍歷去執行。首先就是得到定時任務的開始時間和結束時間去判斷定時任務與現在時間是個什麼關係

int isExecute = TimeUtils.isTime(timingBeans.get(i).getStart(),timingBeans.get(i).getEnd());

 這裡附上TimeUtils工具類的程式碼,這個類主要是對時間的一些相關操作,這裡我得到的定時時間都是小時和分鐘,所以需要我自己做一些跨天的判斷,跨度最長不過24小時。

package com.hx.station.utils;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Locale;

/**
 * 時間相關操作工具類
 *
 * @author snow
 *
 * @version 1.0
 *
 * @since 2018年11月3號 11:37
 */
public class TimeUtils {

    private static SimpleDateFormat dateFormat;
    //    定時方案未到執行時間
    public static final int notStart = 1;
    //    定時方案到了已經開始的時間
    public static final int started = 2;
    //    定時方案已過了執行時間
    public static final int finished = 3;

    //現在時間的毫秒數
    public static long nowDate;

    private static Calendar now;
    private static Calendar start;
    private static Calendar end;


    /**
     * 判斷定時方案的執行時間在哪個執行階段
     *
     * @param startDate 開始時間
     *
     * @param endDate   結束時間
     *
     * @return  執行階段
     */
    public static int isTime(String startDate, String endDate){
        dateFormat = new SimpleDateFormat("HH:mm", Locale.getDefault());
        Date nowTime = null;
        Date startTime = null;
        Date endTime = null;
        try{
            //1970年到現在的毫秒數
            nowDate = System.currentTimeMillis();
            //一下三個time得到的時間年月分都是1970年的所以不能直接用,只用來判斷執行時間段
            nowTime = dateFormat.parse(dateFormat.format(new Date()));
            startTime = dateFormat.parse(startDate);
            endTime = dateFormat.parse(endDate);
        } catch (ParseException e) {
            e.printStackTrace();
        }

        now = Calendar.getInstance();
        now.setTime(nowTime);
        start = Calendar.getInstance();
        start.setTime(startTime);
        end = Calendar.getInstance();
        end.setTime(endTime);

        //這裡是將現在時間的跟開始時間和結束時間作比較
        if (start.after(end)){
            if (now.before(start) && now.after(end)){
                return notStart;
            }
            return started;
        }else if (now.after(start) && now.before(end)){
            return started;
        }else if(now.before(start)){
            return notStart;
        }
        return finished;
    }

    /**
     * 開始執行時間距離現在時間相差得毫秒數
     *
     * @return  毫秒數
     */
    public static long startToNowTimeMillis() {
        return start.getTimeInMillis() - now.getTimeInMillis();
    }

    /**
     * 執行結束時間到現在時間相差得毫秒數
     *
     * @return  毫秒數
     */
    public static long endToNowTimeMillis() {
        return end.getTimeInMillis() - now.getTimeInMillis();
    }

    /**
     * 現在時間距離開始執行時間相差得毫秒數
     *
     * @return  毫秒數
     */
    public static long nowToStartTimeMills() {
        return now.getTimeInMillis() - start.getTimeInMillis();
    }

    /**
     * 現在時間到結束時間的毫秒數
     *
     * @return  毫秒數
     */
    public static long nowToEndTimeMills() {
        return now.getTimeInMillis() - end.getTimeInMillis();
    }


}

接著對程式執行的安卓版本做了判斷,原因是我們主要使用AlarmManage的set()方法執行一次,setRepeating()方法去重複執行定時,但是這兩個方法在API19上可能會出現執行時間不準或者只執行一次的問題,這裡因為實際開發的環境是4.2.2所以沒有對大於19的setWindow()方法做拓展。

//現在時間在執行時間段中間
                        if(Build.VERSION.SDK_INT < 19){
                            //立即執行一次
                            AlarmManagerUtils.getInstance(mActivity).set(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate);
                            //第二天到了開始時間執行,間隔一天執行一次
                            AlarmManagerUtils.getInstance(mActivity).setRepeating(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i+99,TimeUtils.nowDate+86400000-TimeUtils.nowToStartTimeMills(),INTERVAL_DAY);
                        } else {
                            AlarmManagerUtils.getInstance(mActivity).setWindow(timingBeans.get(i).getParam(),CMD_DEVICE_VOLUME,i,TimeUtils.nowDate,0);
                        }

可以看到我是調AlarmManagerUtils得方法去執行定時的,這個工具類是對AlarmManager做的一個簡單封裝,是本文的核心,在介紹這個類之前先大致講下AlarmManage得用法。

1.獲取AlarmManager物件;

2.建立PendingIntent物件;

3.設定執行任務的時間和週期。

AlarmManager是一個系統服務,在Android應用中可以通過Context物件的getSystemService()方法來獲取AlarmManager物件,如下程式碼所示:

AlarmManager aManager=(AlarmManager)getSystemService(Service.ALARM_SERVICE);

獲取了AlarmManager物件之後就可以呼叫它的方法來設定定時啟動指定元件。 

  • set(int type,long triggerAtTime,PendingIntent operation):該方法用於設定一次性鬧鐘,第一個引數表示鬧鐘型別,第二個引數表示鬧鐘執行時間,第三個引數表示鬧鐘響應動作。

  •  setInexactRepeating(int type,long triggerAtTime,long interval, PendingIntent operation):設定一個非精確的週期性任務。任務近似地以interval引數指定的時間間隔執行,如果果由於某些原因(如垃圾回收或其他後臺活動)使得某一個任務延遲執行了,那麼系統就會調整後續任務的執行時間,保證不會因為一個任務的提前或滯後而影響到所有任務的執行,這樣看來,任務就沒有精確地按照interval引數指定的間隔執行。執行的間隔時間不是固定的。
  • setRepeating(int type,long triggerAtTime,long interval,PendingIntent operation):設定一個週期性執行的定時任務,和上面的方法相比,這個方法執行的是精確的定時任務,系統會盡量保證時間間隔固定不變,如果某一個任務被延遲了,那麼後續的任務也相應地被延遲。第一個引數表示鬧鐘型別,第二個引數表示鬧鐘首次執行時間,第三個引數表示鬧鐘兩次執行的間隔時間,第三個引數表示鬧鐘響應動作。

上面幾個方法中幾個引數含義如下: 

1. type 定時任務的型別,該引數可以接收如下值:

  • ELAPSED_REALTIME:表示鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用相對時間(相對於系統啟動開始),狀態值為3。
  • ELAPSED_REALTIME_WAKEUP: 表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用相對時間。狀態值為2。
  • RTC:表示鬧鐘在手機睡眠狀態下不可用,該狀態下鬧鐘使用絕對時間(即系統時間)。當系統呼叫System.currentTimeMillis()方法的返回值與triggerAtTime相等時啟動operation所對應的元件,狀態值為1。
  • RTC_WAKEUP:表示鬧鐘在睡眠狀態下會喚醒系統並執行提示功能,該狀態下鬧鐘也使用絕對時間,狀態值為0。
  • AlarmManager.POWER_OFF_WAKEUP表示鬧鐘在手機關機狀態下也能正常進行提示功能,所以是5個狀態中用的最多的狀態之一,該狀態下鬧鐘也是用絕對時間,狀態值為4。該型別據說受SDK影響,但是我查了半天也沒查到哪個版本能用。

2. triggerAtTime 定時任務首次觸發的時間,是一個毫秒值,該引數值的含義受type引數影響,二者具體的對應關係如下: 

鬧鐘的第一次執行時間,以毫秒為單位,可以自定義時間,不過一般使用當前時間。需要注意的是,本屬性與第一個屬性(type)密切相關,如果第一個引數對 應的鬧鐘使用的是相對時間(ELAPSED_REALTIME和ELAPSED_REALTIME_WAKEUP),那麼本屬性就得使用相對時間(相對於 系統啟動時間來說),比如當前時間就表示為:SystemClock.elapsedRealtime();如果第一個引數對應的鬧鐘使用的是絕對時間 (RTC、RTC_WAKEUP、POWER_OFF_WAKEUP),那麼本屬性就得使用絕對時間,比如當前時間就表示 為:System.currentTimeMillis()。

3. interval 表示兩次鬧鐘執行的間隔時間,也是以毫秒為單位。

INTERVAL_DAY設定鬧鐘,間隔一天

INTERVAL_HALF_DAY設定鬧鐘,間隔半天

INTERVAL_FIFTEEN_MINUTES設定鬧鐘,間隔15分鐘

INTERVAL_HALF_HOUR設定鬧鐘,間隔半個小時

INTERVAL_HOUR設定鬧鐘,間隔一個小時

4. operation 是一個PendingIntent物件,代表鬧鐘需要執行的動作,如啟動Activity、Service,傳送廣播等。

PendingIntent是Intent的封裝類,代表一個延遲執行的意圖。需要注意的是,如果希望到設定的時間啟動Service,則應該採用PendingIntent.getService(Context context, int requestCode, Intent intent, int flags)方法來獲取PendingIntent物件;如果希望到設定的時間傳送Broadcast,則PendingIntent物件的獲取就應該採用PendingIntent.getBroadcast(Context context, int requestCode, Intent intent, int flags)方法;如果希望到設定的時間啟動Activity,則PendingIntent物件的獲取就應該採用PendingIntent.getActivity(Context context, int requestCode, Intent intent, int flags)方法。如果這三種方法錯用了的話,雖然不會報錯,但是看不到鬧鐘提示效果。另外,還有一個PendingIntent.getActivities(Context context, int requestCode, Intent[] intents, int flags)方法,允許傳入一個Intent陣列,這樣就可以同時啟動多個Activity。

使用步驟:

1)獲得ALarmManager例項 ALarmManager am=(ALarmManager)getSystemService(ALARM_SERVICE);

2)定義一個PendingIntent發出廣播

3)呼叫ALarmManager方法,設定定時或重複提醒

4)取消提醒:

所以一個流程是:

	         // 獲取AlarmManager物件
		AlarmManager aManager=(AlarmManager)getSystemService(Service.ALARM_SERVICE);
		Intent intent=new Intent();
		// 啟動一個名為DialogActivity的Activity
		intent.setClass(this, DialogActivity.class);
		// 獲取PendingIntent物件
		// requestCode 引數用來區分不同的PendingIntent物件
		// flag 引數常用的有4個值:
		// FLAG_CANCEL_CURRENT 當需要獲取的PendingIntent物件已經存在時,先取消當前的物件,再獲取新的;
		// 	FLAG_ONE_SHOT 獲取的PendingIntent物件只能使用一次,再次使用需要重新獲取
		// 	FLAG_NO_CREATE 如果獲取的PendingIntent物件不存在,則返回null
		//	FLAG_UPDATE_CURRENT 如果希望獲取的PendingIntent物件與已經存在的PendingIntent物件相比,如果只是Intent附加的資料不		//	同,那麼當前存在的PendingIntent物件不會被取消,而是重新載入新的Intent附加的資料
		PendingIntent pi=PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
		// 設定定時任務,這裡使用絕對時間,即使休眠也提醒,程式啟動後過1s鍾會啟動新的Activity
		aManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis()+1000, pi);

 這裡需要注意requestCode,因為我會有多個定時,所以需要每個定時的PendingIntent 物件不一樣,所以每次newPendingIntent,requestCode都不能一樣,其次是flag,因為我定時任務調節亮度是有具體調節值得,一開始用得網上得程式碼直接傳得0,導致我每次在廣播接收到的值都是第一次傳送得值,之後發現問題改為FLAG_CANCEL_CURRENT就正常了。

其中FLAG_UPDATE_CURRENT是最常用的 描述的Intent有更新的時候需要用到這個flag去更新你的描述,否則元件在下次事件發生或時間到達的時候extras永遠是第一次Intent的extras。使用FLAG_CANCEL_CURRENT也能做到更新extras,只不過是先把前面的extras清除,FLAG_CANCEL_CURRENT和FLAG_UPDATE_CURRENT的區別在於能否新new一個Intent,FLAG_UPDATE_CURRENT能夠新new一個Intent,而FLAG_CANCEL_CURRENT則不能,只能使用第一次的Intent。

接著貼上AlarmManagerUtils工具類的程式碼,可以看到我用了懶漢單例,為的是在每次建立一個定時任務的時候都儲存PendingIntent物件到list中,避免每次new這個類被清空。PendingIntent物件是取消定時的關鍵,而且當會存在多個定時的時候new這個物件的時候requestCode一定要跟之前的不一樣,不然會覆蓋之前相同requestCode的定時任務。

package com.hx.station.utils;

import android.app.Activity;
import android.app.AlarmManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.annotation.RequiresApi;

import com.hx.station.receiver.TimingPlanReceiver;

import java.util.ArrayList;
import java.util.List;

/**
 * AlarmManager工具類,用來執行定時任務
 *
 * @author  snow
 *
 * @version 1.0
 *
 * @since 2018年11月9日
 */
public class AlarmManagerUtils {

    private Activity activity;
    private AlarmManager alarmManager;
    private Intent intent;
    private List<PendingIntent> list;
    private static AlarmManagerUtils alarmManagerUtils;

    /**
     * 使用單例模式,避免重複new物件造成資料丟失
     *
     * @param activity  上下文
     *
     * @return  例項
     */
    public static AlarmManagerUtils getInstance(Activity activity) {
        if (alarmManagerUtils == null) {
            alarmManagerUtils = new AlarmManagerUtils(activity);
        }
        return alarmManagerUtils;
    }

    /**
     * 單例模式私有化構造方法,對AlarmManager和Intent進行初始化
     *
     * @param activity  上下文
     */
    private AlarmManagerUtils(Activity activity){
        this.activity = activity;
        alarmManager = (AlarmManager) activity.getSystemService(Context.ALARM_SERVICE);
        intent = new Intent(activity, TimingPlanReceiver.class);
    }

    /**
     * 開啟按時間段重複執行的定時,適用於小於安卓API19
     *
     * @param param 執行命令的引數
     *
     * @param cmd   執行哪個命令
     *
     * @param requestCode   保證PendingIntent唯一
     *
     * @param triggerAtMillis   開始執行時間
     *
     * @param intervalMillis    間隔執行時間
     */
    public void setRepeating(String param, int cmd, int requestCode, long triggerAtMillis, long intervalMillis){
        intent.putExtra("param",param);
        intent.putExtra("cmd",cmd);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, requestCode,intent,PendingIntent.FLAG_CANCEL_CURRENT);
        list.add(pendingIntent);
        alarmManager.setRepeating(AlarmManager.RTC_WAKEUP,triggerAtMillis,intervalMillis,pendingIntent);
    }

    /**
     * 開啟只執行一次的定時,適用於小於安卓API19
     *
     * @param param 命令引數
     *
     * @param cmd   執行命令
     *
     * @param requestCode   保證PendingIntent唯一
     *
     * @param triggerAtMillis   開始執行時間
     */
    public void set(String param,int cmd,int requestCode,long triggerAtMillis){
        intent.putExtra("param",param);
        intent.putExtra("cmd",cmd);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, requestCode,intent,PendingIntent.FLAG_CANCEL_CURRENT);
        list.add(pendingIntent);
        alarmManager.set(AlarmManager.RTC_WAKEUP,triggerAtMillis,pendingIntent);
    }

    /**
     * 開啟只執行一次的定時,適用於大於等於安卓API19
     *
     * @param param 命令引數
     *
     * @param cmd   執行命令
     *
     * @param requestCode   保證PendingIntent唯一
     *
     * @param triggerAtMills    開始執行時間
     *
     * @param windowLengthMillis    延遲多久執行
     */
    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
    public void setWindow(String param, int cmd, int requestCode, long triggerAtMills, long windowLengthMillis){
        intent.putExtra("param",param);
        intent.putExtra("cmd",cmd);
        PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, requestCode,intent,PendingIntent.FLAG_CANCEL_CURRENT);
        list.add(pendingIntent);
        alarmManager.setWindow(AlarmManager.RTC_WAKEUP,triggerAtMills,windowLengthMillis,pendingIntent);
    }

    /**
     * 取消定時
     */
    public void alarCancel(){
        if (null == list){
            list = new ArrayList<>();
        }
        if (list.size() > 0){
            for (int i = 0; i < list.size(); i++){
                alarmManager.cancel(list.get(i));
            }
            list.clear();
        }
    }
}

 可以看到該類主要就是定義了3個建立定時任務的方法和一個取消所有定時任務的方法這裡主要講下取消。呼叫AlarmManager的cancel()方法,傳入建立定時的PendingIntent物件,這裡因為我每次建立定時的時候把它儲存到了list中,所以遍歷一個一個取消,這個方法我是在每次執行executeTimingPlanHandle()方法會去呼叫。

接著具體看下我的廣播處理了哪些問題。首先我在廣播中定義了一個介面,讓具體實現調節亮度、音量、開關屏的類去實現這三個方法。因為廣播被呼叫的時候會執行onReceive(),所以首先獲取intent傳過來的cmd命令判斷應該執行哪個命令,接著獲取命令的值,接著通過介面回撥具體去執行相關操作。

package com.hx.station.receiver;


import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.text.TextUtils;

import com.hx.station.tools.Logger;

import static com.hx.station.listener.CommandNum.CMD_DEVICE_BRIGHTNESS;
import static com.hx.station.listener.CommandNum.CMD_DEVICE_POWER;
import static com.hx.station.listener.CommandNum.CMD_DEVICE_VOLUME;

/**
 * 執行定時任務的具體操作
 *
 * @author snow
 *
 * @version 1.0
 *
 * @since 2018年11月01日
 */
public class TimingPlanReceiver extends BroadcastReceiver {

    private static final String TAG = "TimingPlanReceiver";

    private static CallBackExecuteCmd callBackExecuteCmd;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (TextUtils.isEmpty(intent.getStringExtra("param"))){
            return;
        }
        switch (intent.getIntExtra("cmd",0)){
            case CMD_DEVICE_VOLUME:
                int volume = Integer.parseInt(intent.getStringExtra("param"));
                if (callBackExecuteCmd != null) {
                    callBackExecuteCmd.callbackTimingVolume(volume);
                    Logger.i(TAG, "音量定時方案執行成功param:" + volume);
                }
                break;
            case CMD_DEVICE_BRIGHTNESS:
                int brightness = Integer.parseInt(intent.getStringExtra("param"));
                if (callBackExecuteCmd != null) {
                    callBackExecuteCmd.callbackTimingBrightness(brightness);
                    Logger.i(TAG, "亮度定時方案執行成功param:" + brightness);
                }
                break;
            case CMD_DEVICE_POWER:
                String power = intent.getStringExtra("param");
                if (callBackExecuteCmd != null) {
                    callBackExecuteCmd.callbackTimingPowerSwitch(power);
                    Logger.i(TAG,"電源定時方案執行成功param:" + power);
                }
                break;
        }
    }

    public interface CallBackExecuteCmd {

        void callbackTimingVolume(int param);

        void callbackTimingBrightness(int param);

        void callbackTimingPowerSwitch(String param);

    }

    /**
     * 給介面賦初始值
     *
     * @param executeCmd    介面初始值
     */
    public static void setOnCallBackExecuteCmd(CallBackExecuteCmd executeCmd){
        callBackExecuteCmd = executeCmd;
    }

}

 最後,在實際測試中發現,這個定時在程式程序死了之後是不會執行的,因為本身開發的特殊性(程式死了會叫起來),所以我直接在activity的onStart()方法中去讀表了,如果定時表有資料就會把讀到的資料給executeTimingPlanHandle()去執行,從而達到定時一直執行。

PS:因為我本身只是一個學了將近兩個月安卓的小菜雞,所以知識面還遠遠不夠,紀錄下來也只是方便自己以後查閱,所以如果您看到這裡覺得本文哪裡寫的不對或者有更好的見解,歡迎留言,謝謝。最後感謝cuisoap、小蝦米有鯊魚夢、mynameis----你猜,部分程式碼見解來源於他們,謝謝。