问题背景
需要记录用户播放音频的时长,我在一个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() {
//每隔一秒会执行一次
}
};
}