Android闹钟设置的解决方案
android设置闹钟并不像ios那样这么简单,做过android设置闹钟的开发者都知道里面的坑有多深。下面记录一下,我解决android闹钟设置的解决方案。
主要问题
1、api19开始alarmmanager的机制修改。
2、应用程序被kill掉后,设置的闹钟不响。
3、6.0以上进入doze模式会使jobscheduler停止工作。
4、手机设置重启后,闹钟失效问题。
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 .stringtolong(recordtime, timeutils.no_second_format), sender); }
这样,保证闹钟在应用程序没有被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); startforeground(gray_service_id, new notification()); } //发送唤醒广播来促使挂掉的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, system.currenttimemillis(), alarm_interval, operation); } } /** * 给 api >= 18 的平台上用的灰色保活手段 */ public static class daemoninnerservice extends service { @override public void oncreate() { log.i(log_tag, "innerservice -> oncreate"); super.oncreate(); } @override public int onstartcommand(intent intent, int flags, int startid) { log.i(log_tag, "innerservice -> onstartcommand"); startforeground(gray_service_id, new notification()); //stopforeground(true); stopself(); return super.onstartcommand(intent, flags, startid); } @override public ibinder onbind(intent intent) { throw new unsupportedoperationexception("not yet implemented"); } @override 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创建一个定时的任务,定时检测闹钟服务是否存在,没在存在则重新启动闹钟服务。(这里我设置每一分钟检测一次闹钟服务)
在进入应用程序的时候检测当前系统是否是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; }else { 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方法来捕获用户的选择。如,代码如下:
@override 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){ if (requestcode == batteryutils.request_ignore_battery_code){ 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()方法,调用闹钟服务的重设闹钟方法。代码如下:
@override 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是不会停止服务的。
最后
以上并不代表所有的android手机的闹钟都可以用,这只是尽最大的可能保证大部分的手机。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。