这里有新鲜出炉的精品教程,程序狗速度看过来!
Android 是一种基于 Linux 的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由 Google 公司和开放手机联盟领导及开发。尚未有统一中文名称,中国大陆地区较多人使用 "安卓" 或 "安致"。
这篇文章主要为大家详细介绍了 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 可以先阅读这篇文章。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 模式做特殊的处理 - 忽略电池的优化。
1). 在 Manifest.xml 中加入权限
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 是不会停止服务的。具体可以查看这篇文章。here
来源: http://www.phperz.com/article/17/0702/336599.html