Android闹钟启动时间设置无效问题的解决方法
android开发中,alarmmanager在5.0以上系统,启动时间设置无效的问题
做一个app,需要后台保持发送心跳包。由于锁屏后cpu休眠,导致心跳包线程被挂起,所以尝试使用alarmmanager定时唤醒service发送心跳包。
以下是开启alarmmanager的代码
//开启轮询服务 public static void startpollingservice(context context, int seconds, class<?> cls,string action) { //获取alarmmanager系统服务 alarmmanager manager = (alarmmanager) context .getsystemservice(context.alarm_service); //包装需要执行service的intent intent intent = new intent(context, cls); intent.setaction(action); pendingintent pendingintent = pendingintent.getservice(context, 0, intent, pendingintent.flag_update_current); //触发服务的起始时间 long triggerattime = systemclock.elapsedrealtime(); //使用alarmmanger的setrepeating方法设置定期执行的时间间隔(seconds秒)和需要执行的service manager.setrepeating(alarmmanager.elapsed_realtime, triggerattime, seconds * 1000, pendingintent); }
结果遇到了很奇怪的问题
传入的时间是2500,也就是每2.5秒一次
在红米1s (系统是cm12.1 android5.1.1)上,亮屏(非休眠)状态下它要好几十秒才会唤醒一次,锁屏(休眠)就不会唤醒了
在小米4(系统是miui7 android4.4.4)上,亮屏状态下正常,锁屏状态下就不会唤醒了
我尝试了broadcastreceiver重写onreceive,也试过service重写onstartcommand,都是一样的情况
原因是因为android alarmmanagerservice里面对于repeating alarm 做了限制。
注意:在19以上版本,setrepeating中设置的频繁只是建议值, 5.0 以上的源码中最小值是60s
class alarmmanagerservice extends systemservice { // minimum alarm recurrence interval private static final long min_interval = 60 * 1000; // one minute, in millis void setimpl(int type, long triggerattime, long windowlength, long interval, pendingintent operation, boolean isstandalone, worksource worksource, alarmmanager.alarmclockinfo alarmclock) { if (operation == null) { slog.w(tag, "set/setrepeating ignored because there is no intent"); return; } // sanity check the window length. this will catch people mistakenly // trying to pass an end-of-window timestamp rather than a duration. if (windowlength > alarmmanager.interval_half_day) { slog.w(tag, "window length " + windowlength + "ms suspiciously long; limiting to 1 hour"); windowlength = alarmmanager.interval_hour; } // sanity check the recurrence interval. this will catch people who supply // seconds when the api expects milliseconds. if (interval > 0 && interval < min_interval) { slog.w(tag, "suspiciously short interval " + interval + " millis; expanding to " + (int)(min_interval/1000) + " seconds"); interval = min_interval; } ... } }
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); }
5.0以上的jobscheduler
关于5.0新增jobscheduler·api可以先阅读这篇文章。
在这里利用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模式做特殊的处理-忽略电池的优化。
1).在manifest.xml中加入权限
<uses-permission android:name="android.permission.request_ignore_battery_optimizations"/>
2).在设置闹钟的时候,判断系统是否是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; } }
3).如果没有忽略电池优化的时候,弹出提醒对话框,提示用户进行忽略电池优化操作。代码如下:
/** * 针对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闹钟启动时间设置无效问题的解决方法
-
android TextView多行文本(超过3行)使用ellipsize属性无效问题的解决方法
-
win10自动设置时间打不开问题的解决方法
-
Android闹钟启动时间设置无效问题的解决方法
-
android TextView多行文本(超过3行)使用ellipsize属性无效问题的解决方法
-
vue中设置height:100%无效的问题及解决方法
-
解决Android popupWindow设置背景透明度无效的问题
-
XP下WORKSTATION启动慢导致系统启动时间过长问题的解决方法
-
win10自动设置时间打不开问题的解决方法
-
XP下WORKSTATION启动慢导致系统启动时间过长问题的解决方法