Android(安卓)锁屏后handler计时失效
目录
- 问题背景
- handler失效的原因
- 排查了解到Doze机制
- 锁屏后计时解决方案
问题背景
需要记录用户播放音频的时长,我在一个service中开启handler,每隔一秒用户正在播放,将播放时长++;正常播放都没问题,即使锁屏大部分手机记录都准确,但在华为手机9.0系统中发现锁屏记录时间不准确,接下来就有了一大堆的排查。
handler失效的原因
采用每次都将系统时间和变量进行写入到手机内存中,通过查看时间和变量看问题出在哪里:记录的结果如下,数据正常就是每隔一秒变量正好加1
在记录不准确的手机中获取的数据如下图:变量是每次都加1,但是时间确实有时候出现跳动,两组数据之前有时候相差好几秒,问题就出在这里了,所以导致记录不准确。
根据排查发现handler本来设置的是每隔一秒执行一次,但有时会出现好几秒后才执行一次,在看到handler源码时发现它是通过SystemClock.uptimeMillis()+delayMillis从消息队列获取消息的,
delayMillis:就是间隔时间 如1秒
SystemClock.uptimeMillis():表示从开机到现在的毫秒数(手机睡眠的时间不包括在内)
如果手机短暂休眠,导致SystemClock.uptimeMillis()值没变,那么uptimeMillis值没变可能导致handler定时失效。
下面是往手机文件写内容的代码,不覆盖内容,续写内容。
public void saveStudyToFile(String string) { String filePath = LOCAL_PATH + "clockFile"; File file = new File(filePath); try { // 首先判断文件夹是否存在 if (!file.exists()) { if (!file.mkdirs()) { // 文件夹不存在则创建文件 Toast.makeText(MyApplication.getInstance(), "文件夹创建失败", Toast.LENGTH_SHORT).show(); } } else { File fileWrite = new File(filePath + File.separator + "studyLog.txt"); // 实例化对象:文件输出流=====要想覆盖之前的内容,去掉ture即可 FileOutputStream fileOutputStream = new FileOutputStream(fileWrite, true); // 写入文件 fileOutputStream.write(string.getBytes()); // 清空输出流缓存 fileOutputStream.flush(); // 关闭输出流 fileOutputStream.close(); } } catch (Exception e) { e.printStackTrace(); } }
排查了解到Doze机制
在排查是了解到Android 6.0系统引入Doze机制保护电池,延长电池寿命。
Doze模式可以简单理解为低能耗状态,一些无关运行能停止都停止了。
Doze机制特征是:
1、在息屏30分钟内、手机没有移动并且没有正在充电状态,会进入Doze模式;
2、进入Doze模式后手机会停止网络请求操作,WakeLocks会被忽略失效,AlarmManager会被推迟,系统不再进行WiFi扫描等等;
3、进入Doze模式后,手机会每隔一段时间进入30s的活动区,这段时间手机检测是否有需要处理的操作;
4、进入Doze模式后,Doze模式没有被唤醒,它会逐渐进入深度深眠;
这里借用网上的一张图更好的理解Doze模式:
锁屏后计时解决方案
虽然了解了那么多,但是发现并没解决锁屏后计时不准的问题,那看看我的解决方法吧。
1、使用Timer替换handler(Timer在应用存活期有效,休眠时无法唤醒)
2、设置手机锁屏开屏监听,采用 System.currentTimeMillis()获取锁屏的时间。
下面是监听锁屏的服务代码,至于注册和开启服务自己完善就没问题了。
/** * 监听锁屏的服务 */public class LockService extends Service { private BroadcastReceiver receiver; private long screenOffTime; private static float screenOffStudyTime; @Override public void onCreate() { super.onCreate(); receiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction() == Intent.ACTION_SCREEN_OFF) { LogUtil.e("收到锁屏广播"); screenOffTime = System.currentTimeMillis(); screenOffStudyTime = AlbumClockDBUtils.durationNum; } else if (intent.getAction() == Intent.ACTION_SCREEN_ON) { LogUtil.e("屏幕亮起广播"); //锁屏这段时间时长 秒 long soffTime = (System.currentTimeMillis() - screenOffTime) / 1000; //最新学习时长 int screenOffTime = (int) (soffTime + screenOffStudyTime); } } }; IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_SCREEN_OFF); filter.addAction(Intent.ACTION_SCREEN_ON); registerReceiver(receiver, filter); timer = new Timer(true); timer.schedule(timerTask, 0, 1000); //延时1000ms后执行,立即执行 } private Timer timer; TimerTask timerTask = new TimerTask() { public void run() { //每隔一秒会执行一次 } };}
更多相关文章
- android开发实战之做手机号和邮编查询小程序
- 日本一恶意软件成功入侵270000部安卓手机
- Android手机终端长连接心跳检测自动化测试方案
- 为寻求新增长点 山寨之父MTK发力Android
- 联发科放弃千元Android市场?
- Android(安卓)怎样获得手机信息
- 【Shader】适合 Android(安卓)手机上 GrabPass 方法失效的热扭曲
- Android高效率编码-细节,控件,架包,功能,工具,开源汇总,你想要的这里
- Android(安卓)时间轴的实现