应用启动和退出是全埋点中最基础也最关键的指标。启动埋点区分冷启动/热启动,退出埋点区分退到后台 vs 进程被杀。借助 ProcessLifecycleOwner 和 ActivityLifecycleCallbacks 可以零侵入地追踪应用前后台状态切换。
一、ProcessLifecycleOwner:应用级生命周期
ProcessLifecycleOwner 是 Lifecycle 库提供的全局生命周期感知组件,它监听所有 Activity 的生命周期,在以下时机触发:
- ON_CREATE:首个 Activity 进入 created 状态
- ON_START:首个 Activity 进入 started 状态(应用来到前台)
- ON_RESUME:首个 Activity 进入 resumed 状态
- ON_PAUSE:最后一个 Activity 进入 paused 状态
- ON_STOP:最后一个 Activity 进入 stopped 状态(应用退到后台)
二、启动与退出追踪实现
class AppLifecycleTracker : Application.ActivityLifecycleCallbacks {
companion object { private var foregroundActivityCount = 0 private var isBackground = true private var coldStartTimestamp = 0L }
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { if (foregroundActivityCount == 0) { coldStartTimestamp = System.currentTimeMillis() trackAppLaunch( type = if (savedInstanceState == null) "cold_start" else "warm_start", launchSource = detectLaunchSource(activity) ) } }
override fun onActivityStarted(activity: Activity) { foregroundActivityCount++ if (isBackground && foregroundActivityCount == 1) { isBackground = false trackAppLaunch(type = "hot_start", source = "background") } }
override fun onActivityStopped(activity: Activity) { foregroundActivityCount-- if (foregroundActivityCount == 0) { isBackground = true trackAppExit( type = "background", duration = System.currentTimeMillis() - coldStartTimestamp, reason = detectExitReason() ) } }
override fun onActivityDestroyed(activity: Activity) { if (activity.isFinishing && foregroundActivityCount == 0) { trackAppExit(type = "exit", reason = "user_exit") } }
private fun detectLaunchSource(activity: Activity): String { return when { activity.intent?.action == Intent.ACTION_MAIN -> "launcher" activity.intent?.action == Intent.ACTION_VIEW -> "deep_link" activity.intent?.hasExtra("notification_id") == true -> "notification" else -> "unknown" } }
private fun detectExitReason(): String { return when { isFinishing -> "user_exit" isBackground -> "background" else -> "unknown" } }
private fun trackAppLaunch(type: String, source: String) { AnalyticsSDK.track("app_launch", mapOf( "type" to type, "source" to source, "timestamp" to System.currentTimeMillis(), "session_id" to generateSessionId() )) }
private fun trackAppExit(type: String, duration: Long, reason: String) { AnalyticsSDK.track("app_exit", mapOf( "type" to type, "duration_ms" to duration, "reason" to reason )) } }
class App : Application() { override fun onCreate() { super.onCreate() registerActivityLifecycleCallbacks(AppLifecycleTracker()) } }
|
三、冷/温/热启动的区分
| 启动类型 |
条件 |
特征 |
| 冷启动(Cold) |
进程不存在 |
Application.onCreate → 首个 Activity.onCreate |
| 温启动(Warm) |
进程存在但首个 Activity 被销毁 |
Activity.onCreate 且 Application 已初始化 |
| 热启动(Hot) |
Activity 在后台 |
onStart → onResume,不经过 onCreate |
温启动判断:进程存在 + savedInstanceState != null(Activity 重建)或 foregroundActivityCount == 0 但 coldStartTimestamp > 0。
四、启动耗时测量
class App : Application() { override fun onCreate() { val startTime = SystemClock.elapsedRealtime() super.onCreate() val appInitTime = SystemClock.elapsedRealtime() - startTime } }
|
AOSP 中 ActivityManagerService 使用 ActivityRecord.getLaunchTime() 计算启动耗时,定义在 frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java。
面试常考问题
Q1:ProcessLifecycleOwner 如何感知所有 Activity 生命周期?
ProcessLifecycleOwner 内部注册了 ActivityLifecycleCallbacks,通过计数器跟踪前台 Activity 数量。当计数器从 0→1 时判定 app 进入前台(ON_START),从 1→0 时判定 app 进入后台(ON_STOP)。它使用 Handler 延迟 700ms 来防止 Activity 切换瞬间的误判。源码:androidx.lifecycle.ProcessLifecycleOwner。
Q2:kill 进程和用户按 Home 键如何区分?
无法直接监听 kill 信号。按 Home 键会在 onStop 中捕获;进程被 kill(如系统回收、force stop)则不会有任何回调。做法是在 onActivityStopped / onSaveInstanceState 中持久化时间戳,下次冷启动时检查上次退出是否有正常记录,若无则为异常退出。
Q3:如何区分 launcher 启动和 deeplink 启动?
通过首个 Activity 的 intent.action 和 intent.data 判断:action 为 MAIN 且 category 含 LAUNCHER 时为 launcher 启动;action 为 VIEW 且 data 不为 null 时为 deeplink;通过 referrer 或 PendingIntent 中自定义 extra 字段可区分通知栏点击。