之前写过一个demo来实现定时操作,最近帮别人写项目的时候又用到了这个需求,于是coding起来。
首先来说一下我的实现思路:用户选择好闹钟提醒时间后我要启动一个Service,在这个Service里面设置闹钟,通过闹钟直接打开一个Activity来显示提醒信息。我想要创建一个不在通知栏中显示notification的前台Service,这样就可以让Service一直运行了,除非你在手机的“最近任务”中kill掉这个应用,实现灰色启动Service的方法不详细介绍。
来看看AlarmManager的使用和问题。
根据android官方文档的说明:
在android API 19之前,我们直接使用AlarmManager#set(@AlarmType int type, long triggerAtMillis, PendingIntent operation)来设置一次性闹钟或者使用AlarmManager#setRepeating(@AlarmType int type, long triggerAtMillis,long intervalMillis, PendingIntent operation)来设置重复闹钟,通过这种方式设置闹钟会定时准确送达,即使你的APP没有运行。
在android API 19-android API 23之间,为了最小化唤醒和电池的使用,之前的set方法已经不再像之前那样好用(从API 19开始,Alarm的机制都是非准确传递的),官方提供了新的API进行设置:AlarmManager#setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation)和AlarmManager#setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis, PendingIntent operation)。setExact用来设置某个时间点准时送达,用于对时间要求很高的功能上,而setWindow则用来设置在windowStartMillis到windowStartMillis+windowLengthMillis时间范围内送达,用于对时间要求相对宽松一些的功能,推荐使用setWindow,因为setWindow这个方法更加省电一些。
在android API 23之后,系统引入了低电耗模式和应用待机模式,API再次发生变化,这次需要使用:AlarmManager#setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,PendingIntent operation)和AlarmManager#setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis, PendingIntent operation)。这两个方法允许在低电耗模式下启动闹钟。
因此,为了兼容各个版本,代码就应该这样写:AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(context, ClockAlarmActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra("id", id); //为intent添加需要传递的参数
PendingIntent sender = PendingIntent.getActivity(context, id, intent, PendingIntent.FLAG_CANCEL_CURRENT);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,calMethod(week, calendar.getTimeInMillis()),sender);
} else {
am.setExact(AlarmManager.RTC_WAKEUP, calMethod(week, calendar.getTimeInMillis()),sender);
}
} else {
if (flag == 0) { //一次性闹钟
am.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);
} else { //重复闹钟
am.setRepeating(AlarmManager.RTC_WAKEUP, calMethod(week, calendar.getTimeInMillis()), intervalMillis, sender);
}
}
按照上面编码后测试,发现在亮屏时可以正常响铃,锁屏后就没办法了。可以确定休眠状态影响了AlarmManager的正常运行。解决办法就好办了,只要保持CPU一直在运行就好了,只是这样会耗电。如果你有更好的解决办法,欢迎留言指导!
WakeLock保持CPU持续运转
首先参考网上的代码写一个电源锁工具类,使用单例模式。
核心代码如下:
//获取电源锁,保持该服务在屏幕熄灭时仍然获取CPU时,保持运行
public void acquireWakeLock() {
if (null == wakeLock) {
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, "myapp:PostLocationService");
if (null != wakeLock) {
wakeLock.acquire();
}
}
}
//释放设备电源锁
public void releaseWakeLock() {
if (null != wakeLock) {
wakeLock.release();
wakeLock = null;
}
}
由于我开启了一个Service来设置闹钟,所以我直接在Service#onCreate方法中获取电源锁了。那么什么时候释放电源锁呢?可以在Service#onDestroy和Service#onTaskRemoved中释放电源锁。
测试,ok,在锁屏时可以成功响铃。可是响铃的时候屏幕没有亮啊???有问题就解决呗。
响铃时唤醒屏幕
唤醒屏幕我还是使用了电源管理器。
//解锁屏幕并亮屏
private void wakeUpAndUnlock(Context context) {
//屏锁管理器
KeyguardManager km = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
KeyguardManager.KeyguardLock kl = km.newKeyguardLock("unLock");
//解锁
kl.disableKeyguard();
//获取电源管理器对象
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
//获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是LogCat里用的Tag
wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP |
PowerManager.SCREEN_DIM_WAKE_LOCK, "myapp:bright");
//点亮屏幕
wl.acquire();
}
我在响铃时其实是启动了一个activity,因此我将唤醒屏幕的操作放在了Activity#onResume方法中了,并在onPause方法中释放电源管理器。
//释放
wl.release();
以上工作完成后测试,能够按时响铃并点亮屏幕。任务结束。
经过反复测试,仅有以上代码还不够,还需要将app加入到白名单中。