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
相关产品推荐
相关产品推荐

