android設定鬧鐘并不像ios那樣這麼簡單,做過android設定鬧鐘的開發者都知道裡面的坑有多深。下面記錄一下,我解決android鬧鐘設定的解決方案。
主要問題
api19開始alarmmanager的機制修改。
應用程式被kill掉後,設定的鬧鐘不響。
6.0以上進入doze模式會使jobscheduler停止工作。
手機設定重新開機後,鬧鐘失效問題。
api19以上alarmmanager機制的修改
api19之前alarmmanager提供了三個設定鬧鐘的方法,由于業務需求鬧鐘隻需要一次性,是以采用set(int type,long starttime,pendingintent pi);這個方法。從api 19開始,alarmmanager的機制都是非準确傳遞,作業系統将會轉換鬧鐘,來最小化喚醒和電池使用。
由于之前的程式,沒有對api19以上的鬧鐘設定做處理,導緻在4.4以上的手機設定鬧鐘無響應(應用程式沒有被殺死的情況也沒有鬧鐘)。
因些,設定鬧鐘需要根據api的版本進行分别處理設定。代碼如下:
alarmmanager am = (alarmmanager) getactivity()
.getsystemservice(context.alarm_service);
if (build.version.sdk_int >= build.version_codes.kitkat) {
am.setexact(alarmmanager.rtc_wakeup, timeutils
.stringtolong(recordtime, timeutils.no_second_format), sender);
}else {
am.set(alarmmanager.rtc_wakeup, timeutils
}
這樣,保證鬧鐘在應用程式沒有被kill掉的情況鬧鐘。
應用程式被kill掉時的處理
應用程式被kill掉後,設定的鬧鐘失效,這裡利用守護程序以及灰色保活來保證背景鬧鐘服務不被kill掉。當應用程式以及鬧鐘服務被kill掉,守護程序以及灰色保活來重新啟動鬧鐘服務,并且重新設定鬧鐘。
關于守護程序的處理,這裡采用開源的守護程序庫。android-appdaemon
在鬧鐘服務的oncreat加入android-appdaemon這個開源的守護程序。代碼如下:
@override
public void oncreate() {
super.oncreate();
daemon.run(daemonservice.this,
daemonservice.class, daemon.interval_one_minute);
starttimetask();
grayguard();
為進一步保證鬧鐘服務的存活,同加上灰色保活(利用系統的漏洞啟動前台service)。代碼如下:
private void grayguard() {
if (build.version.sdk_int < 18) {
//api < 18 ,此方法能有效隐藏notification上的圖示
startforeground(gray_service_id, new notification());
} else {
intent innerintent = new intent(this, daemoninnerservice.class);
startservice(innerintent);
}
//發送喚醒廣播來促使挂掉的ui程序重新啟動起來
alarmmanager alarmmanager = (alarmmanager) getsystemservice(context.alarm_service);
intent alarmintent = new intent();
alarmintent.setaction(wakereceiver.gray_wake_action);
pendingintent operation = pendingintent.getbroadcast(this,
wake_request_code, alarmintent, pendingintent.flag_update_current);
if (build.version.sdk_int >= build.version_codes.kitkat) {
alarmmanager.setwindow(alarmmanager.rtc_wakeup,
system.currenttimemillis(), alarm_interval, operation);
}else {
alarmmanager.setinexactrepeating(alarmmanager.rtc_wakeup,
/**
* 給 api >= 18 的平台上用的灰色保活手段
*/
public static class daemoninnerservice extends service {
@override
public void oncreate() {
log.i(log_tag, "innerservice -> oncreate");
super.oncreate();
public int onstartcommand(intent intent, int flags, int startid) {
log.i(log_tag, "innerservice -> onstartcommand");
//stopforeground(true);
stopself();
return super.onstartcommand(intent, flags, startid);
public ibinder onbind(intent intent) {
throw new unsupportedoperationexception("not yet implemented");
public void ondestroy() {
log.i(log_tag, "innerservice -> ondestroy");
super.ondestroy();
上面操作盡可能提高鬧鐘服務的存活。但是在5.0以上的手機,利用系統的自帶的clean功能的時候,還是會将鬧鐘服務徹底的幹掉。為了解決5.0以上的問題,這裡引入5.0以上的新特性 jobscheduler。
5.0以上的jobscheduler
關于5.0新增jobscheduler·api可以先閱讀這篇文章。here
在這裡利用5.0以上的jobscheduler建立一個定時的任務,定時檢測鬧鐘服務是否存在,沒在存在則重新啟動鬧鐘服務。(這裡我設定每一分鐘檢測一次鬧鐘服務)
在進入應用程式的時候檢測目前系統是否是5.0以上,如果是則啟動jobscheduler這個服務。代碼如下:
if (build.version.sdk_int >= build.version_codes.lollipop) {
mjobscheduler = (jobscheduler) getsystemservice(context.job_scheduler_service);
jobinfo.builder builder = new jobinfo.builder(job_id,
new componentname(getpackagename(), jobschedulerservice.class.getname()));
builder.setperiodic(60 * 1000); //每隔60秒運作一次
builder.setrequirescharging(true);
builder.setpersisted(true); //設定裝置重新開機後,是否重新執行任務
builder.setrequiresdeviceidle(true);
if (mjobscheduler.schedule(builder.build()) <= 0) {
//if something goes wrong
}
其中的builder.setpersisted(true); 方法是裝置重新開機後,是否重新執行任務,在這測過是可以重新啟動任務的。
上面的操作進一步保證了鬧鐘服務被kill掉後,重新啟動服務。但是在6.0以上引入了doze模式,當6.0以上的手機進入這個模式後,便會使jobscheduler停止工作。
6.0以上doze模式的處理
為了讓jobscheduler可以在6.0以上進入doze模式工作,這裡針對6.0以上的doze模式做特殊的處理-忽略電池的優化。
在manifest.xml中加入權限。
<uses-permission android:name="android.permission.request_ignore_battery_optimizations"/>
在設定鬧鐘的時候,判斷系統是否是6.0以上,如果是,則判斷是否忽略電池的優化。判斷是否忽略電池優化代碼如下:
targetapi(build.version_codes.m)
public static boolean isignoringbatteryoptimizations(activity activity){
string packagename = activity.getpackagename();
powermanager pm = (powermanager) activity
.getsystemservice(context.power_service);
if (pm.isignoringbatteryoptimizations(packagename)) {
return true;
return false;
如果沒有忽略電池優化的時候,彈出提醒對話框,提示使用者進行忽略電池優化操作。代碼如下:
* 針對n以上的doze模式
*
* @param activity
public static void isignorebatteryoption(activity activity) {
if (build.version.sdk_int >= build.version_codes.m) {
try {
intent intent = new intent();
string packagename = activity.getpackagename();
powermanager pm = (powermanager) activity.getsystemservice(context.power_service);
if (!pm.isignoringbatteryoptimizations(packagename)) {
// intent.setaction(settings.action_ignore_battery_optimization_settings);
intent.setaction(settings.action_request_ignore_battery_optimizations);
intent.setdata(uri.parse("package:" + packagename));
activity.startactivityforresult(intent, request_ignore_battery_code);
}
} catch (exception e) {
e.printstacktrace();
}
在界面重寫onactivityresult方法來捕獲使用者的選擇。如,代碼如下:
protected void onactivityresult(int requestcode, int resultcode, intent data) {
if (resultcode == result_ok) {
if (requestcode == batteryutils.request_ignore_battery_code){
//todo something
}else if (resultcode == result_canceled){
toastutils.show(getactivity(), "請開啟忽略電池優化~");
補充
當應用程式被kill掉,但是鬧鐘的服務沒有被kill掉的,這時候又設定了鬧鐘。這就意味着設定的鬧鐘沒有放到鬧鐘服務那裡。是以這種情況,設定的鬧鐘會失效。為了解決這種情況,利用aidl(鬧鐘服務在另一個程序的需要程序間通信)調用鬧鐘服務的重新設定鬧鐘方法重設鬧鐘。
在應用程式的oncreat()方法啟動鬧鐘服務,然後再綁定鬧鐘服務。
private void initalarmservice() {
startservice(new intent(this, daemonservice.class));//啟動鬧鐘服務
if (build.version.sdk_int >= build.version_codes.lollipop) {
//jobscheduler
...
//綁定鬧鐘服務
intent intent = new intent(this, daemonservice.class);
intent.setaction("android.intent.action.daemonservice");
bindservice(intent, mconnection, context.bind_auto_create);
在ondestroy()方法,調用鬧鐘服務的重設鬧鐘方法。代碼如下:
protected void ondestroy() {
super.ondestroy();
try {//判斷是否有鬧鐘,沒有則關閉鬧鐘服務
string alarm = localpreferenceshelper.getstring(localpreferenceshelper.alarm_clock);
if (daemonservice != -1 && miremoteservice != null) {
// android.os.process.killprocess(daemonservice);
miremoteservice.resetalarm();
if (!alarm.equals("[]")) {
if (daemonservice != -1) {
startservice(new intent(this, daemonservice.class));
} else {
if (build.version.sdk_int >= build.version_codes.lollipop) {
mjobscheduler.cancel(job_id);
unbindservice(mconnection); //解除綁定服務。
} catch (exception e) {
這裡說明一下,當服務啟動并且被綁定的情況下,unbindservice是不會停止服務的。具體可以檢視這篇文章。here
最後
以上并不代表所有的android手機的鬧鐘都可以用,這隻是盡最大的可能保證大部分的手機。
作者:zhonghanwen
來源:51cto