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

Android闹钟设置的解决方案

程序员文章站 2024-03-06 13:51:08
android设置闹钟并不像ios那样这么简单,做过android设置闹钟的开发者都知道里面的坑有多深。下面记录一下,我解决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的机制都是非准确传递,操作系统将会转换闹钟,来最小化唤醒和电池使用。

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
 .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手机的闹钟都可以用,这只是尽最大的可能保证大部分的手机。

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