如何利用Work Manager在用户指定时段执行屏幕时长追踪任务?
Android实现指定时间区间内追踪屏幕时长(WorkManager+前台服务方案)
一、获取用户指定时间区间
- 用
TimePickerDialog让用户选择开始/结束时间,将结果存入SharedPreferences:
// 选择开始时间 val startTimePicker = TimePickerDialog( context, { _, hour, minute -> saveTimeToPrefs("start_hour", hour) saveTimeToPrefs("start_minute", minute) }, 22, 0, true ) startTimePicker.show() // 选择结束时间 val endTimePicker = TimePickerDialog( context, { _, hour, minute -> saveTimeToPrefs("end_hour", hour) saveTimeToPrefs("end_minute", minute) }, 4, 0, true ) endTimePicker.show() // 存储到SharedPreferences private fun saveTimeToPrefs(key: String, value: Int) { val prefs = context.getSharedPreferences("TimePrefs", Context.MODE_PRIVATE) prefs.edit().putInt(key, value).apply() }
- 处理跨天逻辑:判断
startHour > endHour时,说明区间跨午夜(如22:00到次日4:00),后续计算结束时间需额外加24小时。
二、创建WorkManager定时任务
1. 计算首次任务启动延迟
计算当前时间到下一个用户指定开始时间的间隔:
fun calculateInitialDelay(): Long { val prefs = context.getSharedPreferences("TimePrefs", Context.MODE_PRIVATE) val startHour = prefs.getInt("start_hour", 22) val startMinute = prefs.getInt("start_minute", 0) val now = Calendar.getInstance() val target = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, startHour) set(Calendar.MINUTE, startMinute) set(Calendar.SECOND, 0) } if (target.before(now)) { // 今日开始时间已过,延迟到次日同一时间 target.add(Calendar.DAY_OF_YEAR, 1) } return target.timeInMillis - now.timeInMillis }
2. 构建周期性任务
创建每天重复执行的WorkManager任务,触发前台服务启动:
val workRequest = PeriodicWorkRequestBuilder<ScreenTrackWorker>(24, TimeUnit.HOURS) .setInitialDelay(calculateInitialDelay(), TimeUnit.MILLISECONDS) .build() WorkManager.getInstance(context).enqueueUniquePeriodicWork( "ScreenTrackWork", ExistingPeriodicWorkPolicy.REPLACE, workRequest )
三、实现Worker与前台服务
1. ScreenTrackWorker类
Worker负责启动前台服务,并在结束时间到达时停止服务:
class ScreenTrackWorker( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { override suspend fun doWork(): Result { val prefs = applicationContext.getSharedPreferences("TimePrefs", Context.MODE_PRIVATE) val endHour = prefs.getInt("end_hour", 4) val endMinute = prefs.getInt("end_minute", 0) // 启动前台服务 val intent = Intent(applicationContext, ScreenTrackService::class.java) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { applicationContext.startForegroundService(intent) } else { applicationContext.startService(intent) } // 计算结束时间,处理跨天情况 val now = Calendar.getInstance() val endTime = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, endHour) set(Calendar.MINUTE, endMinute) set(Calendar.SECOND, 0) if (prefs.getInt("start_hour", 22) > endHour && endTime.before(now)) { // 跨天场景,结束时间设为次日 add(Calendar.DAY_OF_YEAR, 1) } } // 等待到结束时间后停止服务 delay(endTime.timeInMillis - now.timeInMillis) applicationContext.stopService(intent) return Result.success() } }
2. ScreenTrackService前台服务
在服务中动态注册屏幕状态广播接收器,统计屏幕点亮时长:
class ScreenTrackService : Service() { private lateinit var screenReceiver: ScreenReceiver private var screenOnTime: Long = 0L override fun onCreate() { super.onCreate() // 注册屏幕状态广播接收器 screenReceiver = ScreenReceiver() val filter = IntentFilter().apply { addAction(Intent.ACTION_SCREEN_ON) addAction(Intent.ACTION_SCREEN_OFF) } registerReceiver(screenReceiver, filter) // 启动前台通知(API 26+强制要求) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { val channel = NotificationChannel( "ScreenTrackChannel", "屏幕时长追踪", NotificationManager.IMPORTANCE_LOW ) val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) val notification = Notification.Builder(this, "ScreenTrackChannel") .setContentTitle("正在追踪屏幕时长") .setSmallIcon(R.drawable.ic_notification) .build() startForeground(1, notification) } } inner class ScreenReceiver : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { when (intent?.action) { Intent.ACTION_SCREEN_ON -> { // 记录屏幕点亮时间戳 screenOnTime = System.currentTimeMillis() } Intent.ACTION_SCREEN_OFF -> { // 计算单次点亮时长并累加 val duration = System.currentTimeMillis() - screenOnTime val prefs = getSharedPreferences("ScreenStats", Context.MODE_PRIVATE) val total = prefs.getLong("total_duration", 0L) + duration prefs.edit().putLong("total_duration", total).apply() } } } } override fun onDestroy() { super.onDestroy() // 注销广播接收器 unregisterReceiver(screenReceiver) } override fun onBind(intent: Intent?): IBinder? = null }
四、关键注意事项
- 后台限制适配:Android 8.0+必须使用前台服务,否则服务会被系统立即终止;静态广播接收器无法接收屏幕开关事件,必须动态注册。
- 跨天逻辑处理:当用户选择的区间跨午夜时,需在计算结束时间时加1天,避免提前停止服务。
- 电池优化:引导用户将应用加入电池优化白名单,否则系统休眠时可能终止服务,导致追踪中断。
- WorkManager可靠性:WorkManager会自动处理设备重启、应用被杀等场景,确保任务在指定时间重新执行。
内容的提问来源于stack exchange,提问作者Sohail Shah
相关产品推荐
相关产品推荐

