需求:

运行建立多个提醒,每个提醒设置一个时间,到达指定时间后跳出提醒窗体

每个提醒有一个执行按钮,点击后保存执行记录,并且当天不再触发提醒窗体

提醒出现后如果任务还没执行,那么需要在30分钟后再次提醒

用户可以关闭全局提醒服务--指闹钟,或针对某个提醒关闭提醒服务,也可以针对某个提醒只关闭当天提醒服务

2个方案

使用的API-17

需要读写SD卡与自启动以及解锁屏幕的权限

A方案:

使用一个"前端服务"--StartFrontServer,在服务里每2分钟跑个任务,这个任务从数据库sqlite读取全部提醒,然后判断那个提醒需要激活,每次也只激活一个

被激活的提醒会更新LastNotifyTime=当前时间,并且在接下来的半个小时内不再触发(如果任务依然没有标记成[已执行]即LastActTime=当天),。

MedicineFrontService类:

1.在启动程序(EMApplication-onCreate方法中)以及收到开机广播或调整时间等时调用下启动服务,这个重载会立即安排个任务

2.runable代码流程参考下文的图片

3.runable只是一个接口不是自己安排线程,这里是关联到主线程调用

4.private Handler handler=new Handler();//不需要编写handler的消息处理代码

5.提醒窗体使用了AlarmAlertWakeLock来在有屏幕锁的情况下显示提醒窗体

public class MedicineFrontService extends Service {
private static final Integer ForegroundId=; /*
* 启动一个前端服务, 在EMApplication中启动
* 这个服务内部有个Handler来每3分钟检测一次是否有要触发的提醒
* 前端服务部容易被回收
*
* (non-Javadoc)
* @see android.app.Service#onCreate()
*/ @SuppressLint("NewApi")
@Override
public void onCreate() {
// TODO Auto-generated method stub
Log.d(TAG, "onCreate"); Notification.Builder builder = new Notification.Builder
(this.getApplicationContext()); //获取一个Notification构造器
Intent nfIntent = new Intent(this, MedicineMainActivity.class); builder.setContentIntent(PendingIntent.
getActivity(this, , nfIntent, )) // 设置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.drawable.ic_launcher)) // 设置下拉列表中的图标(大图标)
.setContentTitle("任务提醒服务") // 设置下拉列表里的标题
.setSmallIcon(R.drawable.ic_launcher) // 设置状态栏内的小图标
.setContentText("服务运行中,使用菜单[暂停服务]退出...") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间 Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults=Notification.DEFAULT_SOUND; //设置为默认的声音 startForeground(ForegroundId, notification); runnable.run(); super.onCreate();
} //===========联合使用Handler与Runable实现类似闹钟的效果 ==== private Handler handler=new Handler();//不需要编写handler的消息处理代码
//Runnable只是一个接口通常需要关联到线程
private Runnable runnable=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//要做的事情
Log.d(TAG, "---Run---");
MNotifyDao dao=new MNotifyDao(getApplicationContext());
List<MNotifyModel> notifies= dao.loadAliveNotifies();
for (MNotifyModel mIt : notifies) { //不需要提醒服务
if(!mIt.isUseNotifyServer()){
continue;
} //检测是否设置忽略了
if(mIt.getLastIgnoreTime()!=null){
String actDate = DateUtil.formatShort(mIt
.getLastIgnoreTime());
String curDate = DateUtil.formatShort(new Date());
if (actDate.compareToIgnoreCase(curDate) == ) {
continue;//跳到下一个
} }
//检测是否执行了今天
if(mIt.getLastActTime()!=null){ String actDate = DateUtil.formatShort(mIt
.getLastActTime());
String curDate = DateUtil.formatShort(new Date());
if (actDate.compareToIgnoreCase(curDate) == ) {
continue;//跳到下一个
} } //设定时间小于当前时间
Date curDate=new Date();
String waringTime= DateUtil.formatShort(curDate)+" " +mIt.getWaringTime() +":00";
if( DateUtil.parse(waringTime).after(curDate)){
continue;
}
//提醒过了需要等30分钟再次提醒
if(mIt.getLastNotifyTime()!=null){
Long diffMicSec=curDate.getTime() -mIt.getLastNotifyTime().getTime();
Log.d(TAG, String.valueOf(diffMicSec));
if(diffMicSec < **){
//小于30分钟
continue;
}
} mIt.setLastNotifyTime(curDate);
dao.update(mIt);
//启动提醒窗体
Intent intent = new Intent(getApplicationContext(), MedicineAlarmActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("NotificationClick");
String note="接收消息:\r\n" +mIt.getMsg() +"\r\n 设定时间:\r\n"+mIt.getWaringTime();
intent.putExtra("Note",note);
intent.putExtra("AddTime", DateUtil.formatLong(curDate));
startActivity(intent);
break;
}
//---------间隔时间--------------------
Integer loopInterval=MedicineSetting.getLoopInterval(getApplicationContext());
handler.postDelayed(this, loopInterval**);
}
}; //=================End=============
@Override
public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand()");
// 在API11之后构建Notification的方式 return Service.START_REDELIVER_INTENT;
} @Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
handler.removeCallbacks(runnable);
stopForeground(true);
super.onDestroy();
} private static final String TAG = "TestMNotify"; @Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
} }

B方案:

MedicineAlarmUtil类

1.在CUD或者启动程序以及执行了某个任务以及收到开机广播或调整时间等时调用下schedule(ctx),这个重载会立即安排个任务

2.而这个任务会在时间到是启动MedicineAlarmIntentService

3.schedule会同时启动守护服务,cancel会关闭守护服务

public class MedicineAlarmUtil {

    public static void schedule(Context ctx){

        schedule(ctx, getPendingIntent(ctx),Calendar.getInstance().getTimeInMillis());
} public static void schedule(Context ctx,PendingIntent pi,Long triggerAtMillis){
AlarmManager am = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
am.set(AlarmManager.RTC_WAKEUP, triggerAtMillis, pi);
MedicineSetting.SetNextFireTime(ctx,DateUtil.formatLong(new Date(triggerAtMillis)));
startFrontServer(ctx);
}
public static void cancel(Context ctx) {
AlarmManager am = (AlarmManager) ctx
.getSystemService(Context.ALARM_SERVICE);
am.cancel(getPendingIntent(ctx));
MedicineSetting.SetNextFireTime(ctx, "未安排");
stopFrontServer(ctx);
}
public static PendingIntent getPendingIntent(Context ctx) {
Intent intent = new Intent(ctx, MedicineAlarmIntentService.class);
intent.setAction("medicineNotify");
PendingIntent pi = PendingIntent.getService(ctx, , intent,
Intent.FLAG_ACTIVITY_NEW_TASK);
return pi;
} //启动前端守护服务服务
private static void startFrontServer(Context ctx){ Intent whiteIntent = new Intent(ctx, MedicineGuardIntentService.class);
ctx.startService(whiteIntent);
}
//关闭前端守护服务
private static void stopFrontServer(Context ctx){ Intent whiteIntent = new Intent(ctx, MedicineGuardIntentService.class);
ctx.stopService(whiteIntent);
}
}

MedicineGuardIntentService类

守护服务的配置

        <service
android:name="cn.fstudio.alarm.MedicineFrontService"
android:enabled="true"
android:exported="false"
android:process=":white"
>
</service>
<service
android:name="cn.fstudio.alarm.MedicineGuardIntentService"
android:enabled="true"
android:exported="false"
android:process=":white"
>

守护服务代码,

public class MedicineGuardIntentService extends Service {
private static final Integer ForegroundId=; /*
*方案B守护进程
*
* (non-Javadoc)
* @see android.app.Service#onCreate()
*/ @SuppressLint("NewApi")
@Override
public void onCreate() {
// TODO Auto-generated method stub
Log.d(TAG, "onCreate"); build();
runnable.run(); super.onCreate();
} @SuppressLint("NewApi")
private void build(){
Notification.Builder builder = new Notification.Builder
(this.getApplicationContext()); //获取一个Notification构造器
Intent nfIntent = new Intent(this, MedicineMainActivity.class); builder.setContentIntent(PendingIntent.
getActivity(this, , nfIntent, )) // 设置PendingIntent
.setLargeIcon(BitmapFactory.decodeResource(this.getResources(),R.drawable.ic_launcher)) // 设置下拉列表中的图标(大图标)
.setContentTitle("提醒服务") // 设置下拉列表里的标题
.setSmallIcon(R.drawable.ic_launcher) // 设置状态栏内的小图标
.setContentText("提醒服务守护服务...") // 设置上下文内容
.setWhen(System.currentTimeMillis()); // 设置该通知发生的时间 Notification notification = builder.build(); // 获取构建好的Notification
notification.defaults=Notification.DEFAULT_SOUND; //设置为默认的声音 startForeground(ForegroundId, notification);
} //===========联合使用Handler与Runable实现类似闹钟的效果 ====
private Handler handler=new Handler();//不需要编写handler的消息处理代码
private volatile boolean firsRun=true;
//Runnable只是一个接口通常需要关联到线程
private Runnable runnable=new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
//要做的事情
Log.d(TAG, "---Run---wait-"); if(firsRun){
firsRun=false; }else {
Log.d(TAG, "---Run-Shcedule--");
if (MedicineSetting.IsUseNotifyServier(getApplicationContext())) {
MedicineAlarmUtil.schedule(getApplicationContext());
}
}
//---------间隔时间--------------------
Integer loopInterval=MedicineSetting.getLoopInterval(getApplicationContext());
Integer guardLoopInterval= loopInterval * ;
//守护者进程轮询时间是设置时间乘以5
handler.postDelayed(this, guardLoopInterval**);
//这个方法不会将代码停在这个位置
//所以下面的提示会打印出来
Log.d(TAG, "---Run-not--wait--");
}
}; //=================End=============
@Override
public int onStartCommand(Intent intent, int flags, int startId) { Log.d(TAG, "onStartCommand()");
// 在API11之后构建Notification的方式 return Service.START_REDELIVER_INTENT;
} @Override
public void onDestroy() {
Log.d(TAG, "onDestroy");
handler.removeCallbacks(runnable);
stopForeground(true);
super.onDestroy();
} private static final String TAG = "TestMNotify"; @Override
public IBinder onBind(Intent intent) {
// TODO Auto-generated method stub
return null;
} }

1.守护服务启动前端服务,每个interval*5的时间调用一下schedule ,interval是参数设置里的轮询时间间隔

2.守护服务在主配置文件中的声明

3.handler.postDelayed(this, guardLoopInterval*1000*60);

//这个方法不会将代码停在这个位置
//所以下面的提示会打印出来
Log.d(TAG, "---Run-not--wait--");

MedicineAlarmIntentService类

1.这个类计算要触发的任务并形成通知文本激发通知窗口

2.在当前需要激发通知完成后(通过先更新lastNotifyTime逻辑上控制)再次计算下一次调度的时间

3.通过AlarmManager安排调度时间,并真正激发通知窗口--如果有通知文本要发布

4.重点是计算下一个要安排的任务执行时间,通过计算每个任务的下一次调用时间,并且选择近的时间做为下一次任务调度时间

参考上面的流程图与下面代码getSortedList

package cn.fstudio.alarm;

import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import cn.fstudio.em.MedicineMainActivity;
import cn.fstudio.medicine.notify.R;
import cn.fstudio.sqlite.dao.MNotifyDao;
import cn.fstudio.sqlite.dao.MNotifyModel;
import cn.fstudio.util.DateUtil;
import android.annotation.SuppressLint;
import android.app.IntentService;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Intent;
import android.graphics.BitmapFactory;
import android.util.Log; public class MedicineAlarmIntentService extends IntentService { @Override
public void onCreate() {
// TODO Auto-generated method stub
//启动下前端进行,只是看看据说这样不容易被回收
AlarmAlertWakeLock.acquireScreenCpuWakeLock(this);
super.onCreate();
}
@Override
public void onDestroy() {
// TODO Auto-generated method stub
AlarmAlertWakeLock.releaseCpuLock();
super.onDestroy();
} private static final String TAG = "TestMNotify";
public MedicineAlarmIntentService(String name) {
super(name);
// TODO Auto-generated constructor stub
}
public MedicineAlarmIntentService() {
super("MedicineAlarmIntentService");
// TODO Auto-generated constructor stub
}
@Override
protected void onHandleIntent(Intent intent) {
// TODO Auto-generated method stub Log.d(TAG, "任务执行--onHandleIntent");
//先安排3分钟后任务再次执行的,防止后面代码执行被中断
//如果后面代码正常执行将覆盖这次安排
Calendar calendar= Calendar.getInstance();
calendar.add(Calendar.MINUTE, );
PendingIntent pi=MedicineAlarmUtil.getPendingIntent(this);
MedicineAlarmUtil.schedule(this, pi, calendar.getTimeInMillis()); List<NInfo> list=GetSortedList();
Date now=new Date();
Date nextFireDate=new Date( now.getTime() + ***); StringBuilder sb=new StringBuilder();
MNotifyDao dao=new MNotifyDao(getApplicationContext());
Boolean needFire=false;
for(int i=;i<list.size();i++){
NInfo it=list.get(i);
if(it.fireNow){
sb.append(it.waringTime +"\r\n");
sb.append(it.msg +"\r\n");
sb.append("----------------------\r\n");
//更新最近提醒时间
it.model.setLastNotifyTime(new Date());
dao.update(it.model);
needFire=true;
}
}
//再次计算排序后的列表
List<NInfo> nextList=GetSortedList();
if(nextList.size()>){
nextFireDate=nextList.get().nextRunTime;
//处理小于3分钟以上的情况
if(nextFireDate.before(new Date())){
nextFireDate=new Date();
}
if( Math.abs(nextFireDate.getTime() - now.getTime())<** ){
nextFireDate= new Date((new Date()).getTime() + **);
}
}
//下次执行超过8小
if(nextFireDate.getTime() > (now.getTime() + ***)){
nextFireDate=new Date(now.getTime() + ***);
}
//安排计算后的下次执行任务
MedicineAlarmUtil.schedule(this, pi, nextFireDate.getTime()); if(needFire) fire(sb.toString());
} private void fire(String note){
Intent intent = new Intent(getApplicationContext(), MedicineAlarmActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setAction("NotificationClick");
intent.putExtra("Note",note);
intent.putExtra("AddTime", DateUtil.formatLong(new Date()));
startActivity(intent);
}
private List<NInfo> GetSortedList(){
List<NInfo> list=new ArrayList<NInfo>();
MNotifyDao dao=new MNotifyDao(getApplicationContext());
List<MNotifyModel> notifies= dao.loadAliveNotifies(); Date now=new Date();
for (MNotifyModel mIt : notifies) {
NInfo info=new NInfo();
info.fireNow=true;
info.recId=mIt.getRecId();
info.msg=mIt.getMsg();
info.waringTime=mIt.getWaringTime();
info.model=mIt;
list.add(info);
//不需要提醒服务
if(!mIt.isUseNotifyServer()){
//当前毫秒数加8个小时
info.nextRunTime=new Date(now.getTime() + ***);
info.fireNow=false;
continue;
} //检测是否设置忽略了
if(mIt.getLastIgnoreTime()!=null){
String actDate = DateUtil.formatShort(mIt
.getLastIgnoreTime());
String curDate = DateUtil.formatShort(new Date());
if (actDate.compareToIgnoreCase(curDate) == ) { Date tomorrow=DateUtil.AddDay( DateUtil.parse(DateUtil.formatShort(new Date())+" " +mIt.getWaringTime()+":00" ),);
info.nextRunTime=tomorrow;
info.fireNow=false;
continue;//跳到下一个
} }
//检测是否执行了今天
if(mIt.getLastActTime()!=null){ String actDate = DateUtil.formatShort(mIt
.getLastActTime());
String curDate = DateUtil.formatShort(new Date());
if (actDate.compareToIgnoreCase(curDate) == ) {
Date tomorrow=DateUtil.AddDay( DateUtil.parse(DateUtil.formatShort(new Date())+" " +mIt.getWaringTime()+":00" ),);
info.nextRunTime=tomorrow;
info.fireNow=false; continue;//跳到下一个
} } //设定时间小于当前时间 String waringTime= DateUtil.formatShort(now)+" " +mIt.getWaringTime() +":00";
if( DateUtil.parse(waringTime).after(now)){
info.nextRunTime=DateUtil.parse(DateUtil.formatShort(new Date())+" " +mIt.getWaringTime()+":00" );
info.fireNow=false;
continue;
}
//提醒过了需要等30分钟再次提醒
String notifyDate = DateUtil.formatShort(mIt.getLastNotifyTime());
String curDate = DateUtil.formatShort(new Date());
//当前时间
info.nextRunTime=now;
if(curDate.compareToIgnoreCase(notifyDate)==){
//是当前时间
Long diffMicSec=now.getTime() -mIt.getLastNotifyTime().getTime();
Log.d(TAG, String.valueOf(diffMicSec));
if(diffMicSec < **){
//最后提醒时间+30分钟
info.nextRunTime=new Date( mIt.getLastNotifyTime().getTime() + **);
info.fireNow=false; continue;
}
} } Collections.sort(list);
return list;
} class NInfo implements Comparable<NInfo>{
public Integer recId;
public Boolean fireNow;
public Date nextRunTime;
public String msg;
public String waringTime;
public MNotifyModel model; @Override
public int compareTo(NInfo another) { return nextRunTime.compareTo(another.nextRunTime);
}
} }

Tips:

1.AlarmManager参考:https://blog.csdn.net/lindroid/article/details/83621590

2.使用Calendar (日历)类来计算下次执行的毫秒数,或者加减Day,Minutes...等等

3.PendingIntent pi = PendingIntent.getService(ctx, 0, intent,
Intent.FLAG_ACTIVITY_NEW_TASK); // 0 就是requestCode,如果这个一样的两次任务安排调用,后一个安排会取代前一个

4.IntentService,Service中的OnCreate以及OnDestroy 在创建或销毁时执行一次,多次在Activity,Service中调用startService,OnCreate等方法不会重复执行.

默认情况下Service是单例方式执行的

5.IntentService 这个是在主线程上执行的不需要将一些如网络操作的方法做异步处理,在onHandleIntent中实现要执行的代码,需要提供一个空参数的构造函数并调用Supper("XXX")

6.B方案中,开启一个前台服务,用来避免进行被回收---到底行不行不清楚。。。

7.,提醒窗体使用了AlarmAlertWakeLock来在有屏幕锁的情况下显示提醒窗体。

一个android任务提醒程序的更多相关文章

  1. android开发------第一个android程序

    好吧,现在我们就一起来写第一个android程序,看它带给了我们什么.sdk的使用和虚拟机的创建我就不说了.项目创建过程先略过,不太重要. 那第一个程序我们能学到什么知识呢?一起看吧.^-^ 在IDE ...

  2. 创建第一个Android应用程序 HelloWorld

    按照博客的进程,今天应该进行程序编写啦,下面让我们开写一个简单的HelloWorld程序. 提示:这里对于如何使用Eclipse创建一个Android程序就不多讲啦,不会的同学可以去查阅相关文档. 程 ...

  3. 第一行代码阅读笔记---详解分析第一个Android程序

    以下是我根据作者的思路,创建的第一个Android应用程序,由于工具强大,代码都自动生成了,如下: package com.example.first_app; import android.os.B ...

  4. Android学习笔记一之第一个Android程序

    /** *Title:总结昨天下午至今天上午的学习成果 *Author:zsg *Date:2017-8-13 / 一.了解Android 1.Android架构 Android大致可分为四层架构:L ...

  5. Android逆向 破解第一个Android程序

    这节正式开始破解编写的第一个Android工程,打开Android Killer,把第一节自己编写的Android apk拖入Android Killer. PS: 如果Android Killer不 ...

  6. Android逆向 编写一个Android程序

    本节使用的Android Studio版本是3.0.1 首先,我们先编写一个apk,后面用这个apk来进行逆向.用Android Studio创建一个新的Android项目,命名为Jhm,一路Next ...

  7. 从零开始,运行一个android例子程序

    电脑上连个eclipse都没装,怎么玩android?一穷二白的你, 下面就跟随我,从零开始,一步一步操作,运行我们的第一个android应用程序.我一直相信,学习开发,只有在调试过程中学的是最快的. ...

  8. delphi 10 Seattle 第一个Android程序

    delphi 10 Seattle 第一个Android程序 1.打开Delphi RAD Studio Seattle,如下图     2.选择black application 点击OK   3. ...

  9. 【Android实验】第一个Android程序与Activity生命周期

    目录 第一个Android程序和Activity生命周期 实验目的 实验要求 实验过程 1. 程序正常启动与关闭 2. 外来电话接入的情况 3. 外来短信接入的情况 4. 程序运行中切换到其他程序(比 ...

随机推荐

  1. service基础概念和操作

    sevice概念介绍 service的实现强烈依赖于kube-DNS组件 新版本k8s安装的是core-DNS 因为每个pod是有生命周期的 为了给客户端访问pod提供一个固定的访问端点 servic ...

  2. NIO与网络编程系统化学习

    1.背景 数据在网络中传输,必然回遇到读写问题.... 2.比较NIO与IO 3.案例演示 3.1.缓冲区演示 package com.wfd360.nio; import org.junit.Tes ...

  3. mysql 使用 MD5函数 校验账号密码

    项目中账号密码需要加密操作,数据库用户表账号是明文,密码是密文,但是前端传递过来的都是密文,所以需要到数据库中加密账号和前端传递过来的密文做校验. 这时候就可以使用md5函数. 使用案例: SELEC ...

  4. centOs7 安装mysql8

    本文环境信息: 软件 版本 CentOS CentOS 7.4 MySQL 8.0.x 安装前先更新系统所有包 sudo yum update 安装 1. 添加 Yum 包 wget https:// ...

  5. 浏览器从输入URL到渲染完页面的整个过程

    从输入URL到渲染出整个页面的过程包括三个部分: 1.DNS解析URL的过程 2.浏览器发送请求与服务器交互的过程 3.浏览器对接收到的html页面渲染的过程 一.DNS解析URL的过程 DNS解析的 ...

  6. redux沉思录

    要素:store.reducer.dispatch/subscribe connect:将业务逻辑剥离到容器类,数据的双向绑定: 数据.操作.UI分离.命令封装 核心思想:对共享状态的维护: 核心代码 ...

  7. RabbitMQ简单介绍+Windows环境安装

    文章目录 1.RabbitMQ简介2.RabbitMQ与其他MQ有什么不同3.RabbitMQ环境安装3.1 安装erlang3.2 安装rabbitmq-server4. RabbitMQ管理平台介 ...

  8. MongoDB shell 2 副本集方法

    rs.initiate()   rs.addArb()   rs.help()   rs.printReplicationInfo() 查看到副本集操作日志 rs.remove() 减少副本集节点 r ...

  9. MongoDB 启动报错

    1.配置MongoDB ls /etc/mongod.conf 可以根据此配置文件启动 或者根据自己需求进行配置文件的变更 重要提醒: 如果变更MongoDB配置文件中:日志与数据文件目录,那么要把这 ...

  10. 交叉引用:Microsoft.NET标准异常 和错误代码对照表

    简介 此表旨在帮助将Windows运行时应用程序错误代码交叉引用到Microsoft.NET标准异常,这些异常可以作为应用程序异常处理技术的一部分. 对照表 .NET Exception (Names ...