需求:

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

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

提醒出现后如果任务还没执行,那么需要在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. anaconda更新tensorflow

    在anaconda prompt中,输入: pip install --upgrade --ignore-installed tensorflow gpu版本输入: pip install --upg ...

  2. MongoDB安装启动教程

    MongoDB安装启动教程 简易教程:鉴于第一次大家使用分布式数据库,提供一个简易教程(也可看老师的PPT或者视频) 1.点击安装包(老师给的),安装目录不要更改,否则后面配置需要改,可能导致装不上 ...

  3. docker学习9-搭建rabbitMQ环境

    前言 docker搭建rabbitMQ环境 下载镜像 rabbitMQ 镜像仓库地址https://hub.docker.com/_/rabbitmq 找带有 mangement的版本,会带后台管理界 ...

  4. Gym - 100962F: Frank Sinatra (树上莫队+bitset)

    题意:给定一棵树,带边权.然后Q次询问,每次给出(u,v),求这个路径上最小的未出现的边权. 思路:树上莫队,求mex可以用分块或者bitset,前者可能会快一点.   莫队过程:求出欧拉序,即记录d ...

  5. 14、python异常处理

    一.什么是异常 在python中,错误触发的异常如下 二.异常的种类 在python中不同的异常可以用不同的类型去标识,一个异常标识一种错误. 1 .常用异常类 AttributeError 试图访问 ...

  6. PureComponent & shouldComponentUpdate

    Called to determine whether the change in props and state should trigger a re-render. Component alwa ...

  7. UI系统的三个主要关系

    1.UI的结构.组织和组件.布局.渲染效率:(系统内置的组件有哪些?) 2.UI与事件的关系: 3.UI与数据的关系:

  8. 范式(Paradigm)是什么?

    Paradigm (范式) 是一个领域中主流的行事套路,它包括 philosophy (理念) 和 methods (方法)两部分.Philosophy (理念) 这个概念很好理解.比如,购物理念就是 ...

  9. JavaScript基础13——面向对象

    什么是面向对象? 面向对象(Object Oriented,OO)是软件开发方法.面向对象的概念和应用已超越了程序设计和软件开发,扩展到如数据库系统,交互式界面,应用结构,应用平台,分布式系统,网络管 ...

  10. 在没有符号和FPO的情况下遍历堆栈(帧指针省略)

    下面是应用程序崩溃转储的调用堆栈.报告的崩溃是名为“HelperLibrary”的模块内的访问冲突,我们没有该模块的符号或源代码.调用堆栈看起来不太可能: 0:000> kv ChildEBP ...