運行創建多個提醒,每一個提醒設置一個時間,到達指定時間後跳出提醒窗體前端
每一個提醒有一個執行按鈕,點擊後保存執行記錄,而且當天再也不觸發提醒窗體java
提醒出現後若是任務還沒執行,那麼須要在30分鐘後再次提醒android
用戶能夠關閉全局提醒服務--指鬧鐘,或針對某個提醒關閉提醒服務,也能夠針對某個提醒只關閉當天提醒服務sql
使用的API-17數據庫
須要讀寫SD卡與自啓動以及解鎖屏幕的權限網絡
使用一個"前端服務"--StartFrontServer,在服務裏每2分鐘跑個任務,這個任務從數據庫sqlite讀取所有提醒,而後判斷那個提醒須要激活,每次也只激活一個app
被激活的提醒會更新LastNotifyTime=當前時間,而且在接下來的半個小時內再也不觸發(若是任務依然沒有標記成[已執行]即LastActTime=當天),。異步
MedicineFrontService類:ide
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=1001; /* * 啓動一個前端服務, 在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, 0, nfIntent, 0)) // 設置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) == 0) { continue;//跳到下一個 } } //檢測是否執行了今天 if(mIt.getLastActTime()!=null){ String actDate = DateUtil.formatShort(mIt .getLastActTime()); String curDate = DateUtil.formatShort(new Date()); if (actDate.compareToIgnoreCase(curDate) == 0) { 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 < 1000*60*30){ //小於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*1000*60); } }; //=================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; } }
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, 120, 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=1002; /* *方案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, 0, nfIntent, 0)) // 設置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; //守護者進程輪詢時間是設置時間乘以5 handler.postDelayed(this, guardLoopInterval*1000*60); //這個方法不會將代碼停在這個位置 //因此下面的提示會打印出來 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, 3); 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() + 1000*60*60*8); StringBuilder sb=new StringBuilder(); MNotifyDao dao=new MNotifyDao(getApplicationContext()); Boolean needFire=false; for(int i=0;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()>0){ nextFireDate=nextList.get(0).nextRunTime; //處理小於3分鐘以上的狀況 if(nextFireDate.before(new Date())){ nextFireDate=new Date(); } if( Math.abs(nextFireDate.getTime() - now.getTime())<1000*60*3 ){ nextFireDate= new Date((new Date()).getTime() + 1000*60*3); } } //下次執行超過8小 if(nextFireDate.getTime() > (now.getTime() + 1000*60*60*8)){ nextFireDate=new Date(now.getTime() + 1000*60*60*8); } //安排計算後的下次執行任務 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() + 1000*60*60*8); info.fireNow=false; continue; } //檢測是否設置忽略了 if(mIt.getLastIgnoreTime()!=null){ String actDate = DateUtil.formatShort(mIt .getLastIgnoreTime()); String curDate = DateUtil.formatShort(new Date()); if (actDate.compareToIgnoreCase(curDate) == 0) { Date tomorrow=DateUtil.AddDay( DateUtil.parse(DateUtil.formatShort(new Date())+" " +mIt.getWaringTime()+":00" ),1); 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) == 0) { Date tomorrow=DateUtil.AddDay( DateUtil.parse(DateUtil.formatShort(new Date())+" " +mIt.getWaringTime()+":00" ),1); 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)==0){ //是當前時間 Long diffMicSec=now.getTime() -mIt.getLastNotifyTime().getTime(); Log.d(TAG, String.valueOf(diffMicSec)); if(diffMicSec < 1000*60*30){ //最後提醒時間+30分鐘 info.nextRunTime=new Date( mIt.getLastNotifyTime().getTime() + 1000*60*30); 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來在有屏幕鎖的狀況下顯示提醒窗體。