欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

Android闹钟启动时间设置无效问题的解决方法

程序员文章站 2023-11-27 15:13:52
android开发中,alarmmanager在5.0以上系统,启动时间设置无效的问题 做一个app,需要后台保持发送心跳包。由于锁屏后cpu休眠,导致心跳包线程被挂...

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是不会停止服务的。具体可以查看这篇文章。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。