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

Kotlin 统计用户应用累计使用时长的最佳实现方案是什么?

安卓Kotlin应用累计停留时长统计实现方案

核心实现逻辑

针对多Activity、MVVM架构的场景,采用全局生命周期监听+Repository层统一封装的方案实现,不需要在单个Activity写冗余逻辑,统计误差极小。


具体实现步骤

  • 全局生命周期监听:通过Application注册ActivityLifecycleCallbacks,维护可见Activity的计数变量:
    • 当计数从0变为1时,代表应用切到前台,记录当前时间戳为本次使用起始时间
    • 当计数从1变为0时,代表应用切到后台,计算当前时间和起始时间的差值,累加到总时长中
      该方式天然规避了Activity跳转、横竖屏切换等场景的统计误差。
  • MVVM层逻辑封装:将所有时长计算、持久化逻辑放在Repository层(MVVM的Model层范畴),和UI层完全解耦:
    • 核心封装三个能力:记录前台起始时间、计算新增时长并累加、查询累计总时长
    • 持久化推荐用Jetpack DataStore,相比SharedPreferences更适配Kotlin协程,无主线程阻塞风险
  • 边界场景兼容:
    • 应用被系统强杀:除了切后台时持久化数据,可额外增加每间隔1分钟自动存储当前已产生的时长增量,避免数据丢失
    • 熄屏场景:熄屏会触发Activity的onStop回调,自动触发切后台逻辑,不会将熄屏时间计入使用时长
    • 多页面跳转:Activity跳转时可见计数不会降到0,不会重复触发起止时间记录,统计精准

核心代码示例

Application全局监听注册

class MyApp : Application() {
    private var visibleActivityCount = 0
    private lateinit var usageTimeRepository: UsageTimeRepository

    override fun onCreate() {
        super.onCreate()
        usageTimeRepository = UsageTimeRepository(this)
        
        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityResumed(activity: Activity) {
                visibleActivityCount++
                if (visibleActivityCount == 1) {
                    // 应用进入前台,记录开始时间
                    lifecycleScope.launch {
                        usageTimeRepository.recordForegroundStartTime(System.currentTimeMillis())
                    }
                }
            }

            override fun onActivityStopped(activity: Activity) {
                visibleActivityCount--
                if (visibleActivityCount == 0) {
                    // 应用进入后台,计算时长累加
                    lifecycleScope.launch {
                        usageTimeRepository.calculateAndAddDuration(System.currentTimeMillis())
                    }
                }
            }
            // 其余生命周期方法空实现即可
            override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
            override fun onActivityStarted(activity: Activity) {}
            override fun onActivityPaused(activity: Activity) {}
            override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {}
            override fun onActivityDestroyed(activity: Activity) {}
        })
    }
}

Repository层实现

class UsageTimeRepository(context: Context) {
    private val dataStore = context.createDataStore(name = "usage_time_config")
    private val TOTAL_USAGE_TIME = longPreferencesKey("total_usage_time")
    private val LAST_FOREGROUND_TIME = longPreferencesKey("last_foreground_time")

    // 记录前台进入时间
    suspend fun recordForegroundStartTime(time: Long) {
        dataStore.edit { it[LAST_FOREGROUND_TIME] = time }
    }

    // 计算本次使用时长并累加
    suspend fun calculateAndAddDuration(backgroundTime: Long) {
        dataStore.edit { pref ->
            val lastStartTime = pref[LAST_FOREGROUND_TIME] ?: 0
            if (lastStartTime > 0 && backgroundTime > lastStartTime) {
                val duration = backgroundTime - lastStartTime
                pref[TOTAL_USAGE_TIME] = (pref[TOTAL_USAGE_TIME] ?: 0) + duration
                pref[LAST_FOREGROUND_TIME] = 0 // 重置避免重复计算
            }
        }
    }

    // 查询累计使用时长,供ViewModel调用给UI层展示
    fun getTotalUsageTime(): Flow<Long> {
        return dataStore.data.map { it[TOTAL_USAGE_TIME] ?: 0 }
            .catch { emit(0) }
    }
}

内容的提问来源于stack exchange,提问作者Mohammed Ali

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.18 13:54:02