需求情景:
接收服务器下发的定时任务:定时亮屏、定时调节音量、定时调节亮度。
具体定时任务:
音量和亮度只需要在开始时间去执行,电源开关(其实是系统开关屏)需要在开始时间开启,结束时间关闭。
具体调节亮度、音量、开关屏操作暂不讲述。
参考博客:
https://blog.csdn.net/wei_chong_chong/article/details/51258336
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----你猜,部分代码见解来源于他们,谢谢。