天天看點

Android鬧鐘設定的解決方案

android設定鬧鐘并不像ios那樣這麼簡單,做過android設定鬧鐘的開發者都知道裡面的坑有多深。下面記錄一下,我解決android鬧鐘設定的解決方案。

Android鬧鐘設定的解決方案

主要問題

api19開始alarmmanager的機制修改。

應用程式被kill掉後,設定的鬧鐘不響。

6.0以上進入doze模式會使jobscheduler停止工作。

手機設定重新開機後,鬧鐘失效問題。

api19以上alarmmanager機制的修改

api19之前alarmmanager提供了三個設定鬧鐘的方法,由于業務需求鬧鐘隻需要一次性,是以采用set(int type,long starttime,pendingintent pi);這個方法。從api 19開始,alarmmanager的機制都是非準确傳遞,作業系統将會轉換鬧鐘,來最小化喚醒和電池使用。

Android鬧鐘設定的解決方案

由于之前的程式,沒有對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

繼續閱讀