You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何利用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 22:36:19