目录
  1. 1. 一、AOSP 源码视角:应用进程的生命周期
    1. 1.1. 1.1 Android 进程生命周期模型
    2. 1.2. 1.2 启动类型定义(AOSP 视角)
  2. 2. 二、冷启动全埋点实现
    1. 2.1. 2.1 启动时间检测
    2. 2.2. 2.2 启动事件采集
  3. 3. 三、ProcessLifecycleOwner:全局前后台感知
    1. 3.1. 3.1 原理与配置
  4. 4. 四、退出检测与异常退出识别
    1. 4.1. 4.1 退出类型
    2. 4.2. 4.2 异常退出检测
  5. 5. 五、启动性能 Vitals 采集
    1. 5.1. 5.1 分段耗时测量
  6. 6. 六、多进程场景处理
  7. 7. 七、架构流程图
  8. 8. 八、神策 / Firebase 集成示例
  9. 9. 九、ProGuard/R8 规则
  10. 10. 面试常考问题
【全埋点方案系列】AppLauncher、AppExit全埋点

应用启动和退出是全埋点中最基础也最关键的指标。启动埋点区分冷启动/热启动,退出埋点区分退到后台 vs 进程被杀。借助 ProcessLifecycleOwnerActivityLifecycleCallbacks 可以零侵入地追踪应用前后台状态切换。

一、AOSP 源码视角:应用进程的生命周期

1.1 Android 进程生命周期模型

Android 的进程生命周期由系统管理,开发者没有直接的「进程创建」和「进程销毁」回调。生命周期层次如下:

系统层面(Zygote → ActivityManagerService)
├── 进程创建:Zygote fork → ActivityThread.main()
├── 进程优先级:oom_adj (0 = 前台 → 1000 = 缓存)
└── 进程销毁:LMK (Low Memory Killer) 或 ActivityManagerService.killProcess()

应用层面(Application → Activity)
├── Application.onCreate() ← 进程创建的第一个应用层回调
├── Activity.onCreate/Start/Resume ← 用户可感知的生命周期
├── Activity.onPause/Stop/Destroy ← 用户离开时的回调(不一定触发)
└── ProcessLifecycleOwner ← 全局前台/后台状态

1.2 启动类型定义(AOSP 视角)

AOSP 的 ActivityManagerService 中使用 ProcessRecord 来跟踪每个进程的状态。启动类型可以这样定义:

启动类型 进程状态 Activity 状态 首个可见回调 典型耗时
冷启动 进程不存在 Application.onCreate → Activity.onResume 500ms-3s
温启动 进程存在 所有 Activity 已销毁 Activity.onCreate (进程存在) 200ms-1s
热启动 进程存在 至少一个 Activity 在后台 Activity.onStart → onResume 50ms-200ms

二、冷启动全埋点实现

2.1 启动时间检测

/**
* App 启动追踪器
*
* 使用 ContentProvider 实现最早时机的初始化
* ContentProvider.onCreate() 在 Application.onCreate() 之前执行
* 这是获取最准确启动时间的关键
*
* AndroidManifest.xml 中声明:
* <provider
* android:name=".tracking.TrackingInitProvider"
* android:authorities="${applicationId}.tracking.init"
* android:exported="false" />
*/
class TrackingInitProvider : ContentProvider() {

companion object {
// 使用 SystemClock.elapsedRealtime() 获取精确的启动时间
// 相比 System.currentTimeMillis(),它不受系统时间调整的影响
@JvmField
var processStartTime: Long = -1L

@JvmField
var applicationStartTime: Long = -1L
}

init {
// 类加载时即为进程启动时间(近似)
// 更精确的做法是在 ContentProvider 的 static 块中记录
// 因为 ContentProvider 是最早的初始化入口
if (processStartTime == -1L) {
processStartTime = SystemClock.elapsedRealtime()
}
}

override fun onCreate(): Boolean {
// 在 Application.onCreate() 之前执行
processStartTime = SystemClock.elapsedRealtime()

val application = context?.applicationContext as? Application
application?.registerActivityLifecycleCallbacks(
AppLaunchTracker()
)

return true
}

override fun query(p0: Uri, p1: Array<out String>?, p2: String?, p3: Array<out String>?, p4: String?): Cursor? = null
override fun getType(p0: Uri): String? = null
override fun insert(p0: Uri, p1: ContentValues?): Uri? = null
override fun delete(p0: Uri, p1: String?, p2: Array<out String>?): Int = 0
override fun update(p0: Uri, p1: ContentValues?, p2: String?, p3: Array<out String>?): Int = 0
}

2.2 启动事件采集

/**
* App 启动追踪的 ActivityLifecycleCallbacks
*/
class AppLaunchTracker : Application.ActivityLifecycleCallbacks {

companion object {
private var foregroundActivityCount = 0
private var isAppInForeground = false
private var sessionId: String = generateSessionId()
private var lastBackgroundTimestamp = 0L

// 首个 Activity 的创建时间(用于计算完整启动耗时)
private var firstActivityCreatedTime = -1L
private var firstActivityResumedTime = -1L

fun generateSessionId(): String = UUID.randomUUID().toString()
}

override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {
if (foregroundActivityCount == 0) {
// === 应用启动 ===
val now = SystemClock.elapsedRealtime()

// 计算 Application 初始化耗时
val appInitDuration = if (TrackingInitProvider.applicationStartTime > 0) {
now - TrackingInitProvider.applicationStartTime
} else { -1L }

// 确定启动类型
val launchType = determineLaunchType(savedInstanceState)

// 确定启动来源
val launchSource = detectLaunchSource(activity)

// 生成新的 session(如果是冷/温启动)
if (launchType == "cold_start") {
sessionId = generateSessionId()
}

firstActivityCreatedTime = now

// 上报启动事件
trackAppLaunch(
type = launchType,
source = launchSource,
processStartTime = TrackingInitProvider.processStartTime,
appInitDuration = appInitDuration,
activity = activity
)
}
}

override fun onActivityStarted(activity: Activity) {
foregroundActivityCount++
if (!isAppInForeground && foregroundActivityCount == 1) {
isAppInForeground = true

// 从后台回到前台 = 热启动
val backgroundDuration = if (lastBackgroundTimestamp > 0) {
System.currentTimeMillis() - lastBackgroundTimestamp
} else { -1L }

trackAppLaunch(
type = "hot_start",
source = "background",
backgroundDurationMs = backgroundDuration,
activity = activity
)
}
}

override fun onActivityResumed(activity: Activity) {
if (firstActivityResumedTime == -1L) {
firstActivityResumedTime = SystemClock.elapsedRealtime()

// 计算完整启动耗时:进程创建 → 首个 Activity onResume
val totalLaunchTime = firstActivityResumedTime - TrackingInitProvider.processStartTime
trackLaunchPerformance(totalLaunchTime)
}
}

override fun onActivityPaused(activity: Activity) {}

override fun onActivityStopped(activity: Activity) {
foregroundActivityCount--
if (foregroundActivityCount == 0) {
isAppInForeground = false
lastBackgroundTimestamp = System.currentTimeMillis()

// 上报退到后台事件
trackAppBackground(sessionId, activity)
}
}

override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
// 持久化 session 状态,用于检测异常退出
}

override fun onActivityDestroyed(activity: Activity) {
// 如果 isFinishing 且是最后一个 Activity → 用户主动退出
if (activity.isFinishing && foregroundActivityCount == 0) {
trackAppExit(type = "user_exit", reason = "activity_finish")
}
}

// ========================
// 启动类型判断
// ========================

private fun determineLaunchType(savedInstanceState: Bundle?): String {
return when {
// 冷启动:进程不存在,Application 未初始化
TrackingInitProvider.applicationStartTime == -1L -> "cold_start"
// 温启动:进程存在,但 Activity 被重建(如系统回收后重启)
savedInstanceState != null -> "warm_start"
// 温启动:进程存在,没有 savedInstanceState 但是从后台启动的
foregroundActivityCount == 0 && lastBackgroundTimestamp > 0 -> "warm_start"
else -> "cold_start"
}
}

// ========================
// 启动来源检测
// ========================

private fun detectLaunchSource(activity: Activity): String {
val intent = activity.intent ?: return "unknown"
return when {
intent.action == Intent.ACTION_MAIN &&
intent.hasCategory(Intent.CATEGORY_LAUNCHER) -> {
// Launcher 桌面图标
"launcher_icon"
}
intent.action == Intent.ACTION_VIEW -> {
if (intent.data != null) "deep_link" else "unknown"
}
intent.hasExtra("notification_id") -> "notification"
intent.hasExtra("widget") -> "widget"
intent.action == Intent.ACTION_SEND -> "share"
intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0 -> "recent"
else -> "other"
}
}

// ========================
// 辅助方法
// ========================

private fun trackAppLaunch(
type: String,
source: String,
processStartTime: Long = -1L,
appInitDuration: Long = -1L,
backgroundDurationMs: Long = -1L,
activity: Activity
) {
AnalyticsSDK.track("app_launch", mapOf(
"launch_type" to type,
"launch_source" to source,
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis(),
"process_start_time" to processStartTime,
"app_init_duration_ms" to appInitDuration,
"background_duration_ms" to backgroundDurationMs,
"first_activity" to activity.javaClass.simpleName,
"intent_action" to (activity.intent?.action ?: ""),
"referrer" to (activity.referrer?.toString() ?: "")
))
}

private fun trackLaunchPerformance(totalLaunchTime: Long) {
// 上报启动性能指标
AnalyticsSDK.track("launch_performance", mapOf(
"total_launch_time_ms" to totalLaunchTime,
"cold_start" to (TrackingInitProvider.applicationStartTime == -1L),
"timestamp" to System.currentTimeMillis()
))
}

private fun trackAppBackground(sessionId: String, lastActivity: Activity) {
AnalyticsSDK.track("app_background", mapOf(
"session_id" to sessionId,
"last_activity" to lastActivity.javaClass.simpleName,
"timestamp" to System.currentTimeMillis()
))
}

private fun trackAppExit(type: String, reason: String) {
AnalyticsSDK.track("app_exit", mapOf(
"exit_type" to type,
"exit_reason" to reason,
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis()
))
}
}

三、ProcessLifecycleOwner:全局前后台感知

3.1 原理与配置

dependencies {
implementation "androidx.lifecycle:lifecycle-process:2.7.0"
}
/**
* 使用 ProcessLifecycleOwner 实现全局前后台监控
*
* ProcessLifecycleOwner 内部通过 ActivityLifecycleCallbacks + 计数器
* + 700ms 延迟通知来实现前后台状态切换。
*
* 源码要点(ProcessLifecycleOwner.java):
* - 使用 Handler 延迟 700ms 发送 ON_STOP 事件
* - 如果 700ms 内有新的 Activity onStart,取消 ON_STOP
* - 目的:防止 Activity A 结束 → Activity B 启动之间的短暂后台误判
*/
class AppLifecycleObserver(
private val analyticsSDK: AnalyticsSDK = AnalyticsSDK.getInstance()
) : DefaultLifecycleObserver {

private var sessionId = UUID.randomUUID().toString()
private var sessionStartTime = System.currentTimeMillis()
private var isFirstLaunch = true

init {
ProcessLifecycleOwner.get().lifecycle.addObserver(this)
}

override fun onCreate(owner: LifecycleOwner) {
// 进程创建时调用(仅一次)
analyticsSDK.track("process_create", mapOf(
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis()
))
}

override fun onStart(owner: LifecycleOwner) {
// App 进入前台(首个 Activity 的 onStart)

if (isFirstLaunch) {
// 冷启动
analyticsSDK.track("app_launch", mapOf(
"launch_type" to "cold_start",
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis()
))
isFirstLaunch = false
} else {
// 热启动:从后台回到前台
analyticsSDK.track("app_launch", mapOf(
"launch_type" to "hot_start",
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis()
))
}
}

override fun onResume(owner: LifecycleOwner) {
// App 进入前台交互状态(首个 Activity 的 onResume)
// 可用于计算完整的启动耗时
}

override fun onPause(owner: LifecycleOwner) {
// 不常用
}

override fun onStop(owner: LifecycleOwner) {
// App 退到后台(最后一个 Activity 的 onStop + 700ms 延迟)
val sessionDuration = System.currentTimeMillis() - sessionStartTime

analyticsSDK.track("app_background", mapOf(
"session_id" to sessionId,
"session_duration_ms" to sessionDuration,
"timestamp" to System.currentTimeMillis()
))

// 持久化 session 信息(用于异常退出检测)
persistSessionState(sessionId, sessionDuration)
}

override fun onDestroy(owner: LifecycleOwner) {
// 进程即将销毁(极少触发)
analyticsSDK.track("app_exit", mapOf(
"session_id" to sessionId,
"timestamp" to System.currentTimeMillis()
))
analyticsSDK.flush() // 强制刷新缓冲区
}

private fun persistSessionState(sessionId: String, duration: Long) {
// 持久化最后 session 状态到 SharedPreferences/MMKV
// 下次启动时检查是否有未正常结束的 session
}
}

四、退出检测与异常退出识别

4.1 退出类型

App 退出
├── 主动退出
│ ├── 用户按返回键退出 (Activity.isFinishing)
│ ├── 调用 finishAffinity() / System.exit()
│ └── 通过通知栏清理
├── 退到后台
│ ├── 按 Home 键
│ ├── 切换到其他 App(Task Switcher)
│ └── 点击通知栏启动其他 App
└── 异常退出(无法正常回调)
├── 系统回收(LMK 杀进程)
├── 用户 Force Stop(设置中强制停止)
├── 崩溃(未捕获异常)
├── ANR 后被系统杀死
└── 设备重启 / 关机

4.2 异常退出检测

/**
* 异常退出检测器
*
* 原理:在关键生命周期节点持久化状态标记
* 下次启动时检查标记是否存在来判断上次是否异常退出
*/
class AbnormalExitDetector(private val context: Context) {

private val prefs: SharedPreferences =
context.getSharedPreferences("tracking_state", Context.MODE_PRIVATE)

companion object {
private const val KEY_LAST_SESSION_ID = "last_session_id"
private const val KEY_LAST_STATE = "last_state"
private const val KEY_LAST_TIMESTAMP = "last_timestamp"
private const val KEY_LAST_ACTIVITY = "last_activity"

// 状态常量
private const val STATE_FOREGROUND = "foreground"
private const val STATE_BACKGROUND = "background"
private const val STATE_DESTROYED = "destroyed"
}

/**
* 在应用启动时调用,检测上次是否异常退出
*/
fun detectOnLaunch(): AbnormalExitInfo? {
val lastSessionId = prefs.getString(KEY_LAST_SESSION_ID, null) ?: return null
val lastState = prefs.getString(KEY_LAST_STATE, STATE_DESTROYED) ?: STATE_DESTROYED
val lastTimestamp = prefs.getLong(KEY_LAST_TIMESTAMP, 0L)
val lastActivity = prefs.getString(KEY_LAST_ACTIVITY, "")

// 如果上次记录的 state 不是 DESTROYED,说明是异常退出
if (lastState != STATE_DESTROYED) {
val abnormalType = when (lastState) {
STATE_FOREGROUND -> "killed_in_foreground" // 前台被杀(LMK)
STATE_BACKGROUND -> "killed_in_background" // 后台被杀(正常 LMK 回收)
else -> "unknown"
}
return AbnormalExitInfo(
lastSessionId = lastSessionId,
abnormalType = abnormalType,
lastState = lastState,
lastTimestamp = lastTimestamp,
lastActivity = lastActivity ?: "unknown"
)
}
return null
}

/**
* 标记当前状态
*/
fun markState(state: String, activityName: String = "") {
prefs.edit()
.putString(KEY_LAST_STATE, state)
.putString(KEY_LAST_ACTIVITY, activityName)
.putLong(KEY_LAST_TIMESTAMP, System.currentTimeMillis())
.apply()
}

/**
* 标记正常退出
*/
fun markNormalExit() {
prefs.edit()
.putString(KEY_LAST_STATE, STATE_DESTROYED)
.apply()
}

data class AbnormalExitInfo(
val lastSessionId: String,
val abnormalType: String,
val lastState: String,
val lastTimestamp: Long,
val lastActivity: String
)
}

// 使用示例
class App : Application() {
private lateinit var abnormalExitDetector: AbnormalExitDetector

override fun onCreate() {
super.onCreate()
abnormalExitDetector = AbnormalExitDetector(this)

// 检测上次是否异常退出
val abnormalInfo = abnormalExitDetector.detectOnLaunch()
if (abnormalInfo != null) {
AnalyticsSDK.track("abnormal_exit_detected", mapOf(
"last_session_id" to abnormalInfo.lastSessionId,
"abnormal_type" to abnormalInfo.abnormalType,
"last_state" to abnormalInfo.lastState,
"last_activity" to abnormalInfo.lastActivity
))
}

// 标记当前为前台状态
abnormalExitDetector.markState("foreground", "")

// 注册生命周期回调...
}
}

五、启动性能 Vitals 采集

5.1 分段耗时测量

/**
* App 启动性能指标采集
* 参照 Google Vitals 的启动时间测量标准
*/
class LaunchPerformanceTracker {

companion object {
// Vitals 定义:冷启动时间 = Application.onCreate 开始 → 首个 Activity 首帧绘制
private var appOnCreateStartTime = -1L
private var appOnCreateEndTime = -1L
private var firstActivityOnCreateStartTime = -1L
private var firstFrameRenderedTime = -1L
}

/**
* 在 Application.onCreate 入口调用
*/
fun markAppOnCreateStart() {
appOnCreateStartTime = SystemClock.elapsedRealtime()
}

fun markAppOnCreateEnd() {
appOnCreateEndTime = SystemClock.elapsedRealtime()
}

fun markFirstActivityOnCreateStart() {
firstActivityOnCreateStartTime = SystemClock.elapsedRealtime()
}

/**
* 通过 ViewTreeObserver 获取首帧绘制时间
*/
fun observeFirstFrame(activity: Activity) {
activity.window.decorView.viewTreeObserver
.addOnDrawListener(object : ViewTreeObserver.OnDrawListener {
override fun onDraw() {
if (firstFrameRenderedTime == -1L) {
firstFrameRenderedTime = SystemClock.elapsedRealtime()
reportLaunchMetrics()
}
activity.window.decorView.viewTreeObserver.removeOnDrawListener(this)
}
})
}

private fun reportLaunchMetrics() {
// Google Vitals 的 TTID (Time To Initial Display) 和 TTFD (Time To Full Display)
val ttiDuration = firstActivityOnCreateStartTime - appOnCreateStartTime

AnalyticsSDK.track("launch_performance", mapOf(
"app_on_create_ms" to (appOnCreateEndTime - appOnCreateStartTime),
"first_activity_on_create_ms" to (firstFrameRenderedTime - firstActivityOnCreateStartTime),
"tti_duration_ms" to ttiDuration,
"ttfd_duration_ms" to (firstFrameRenderedTime - appOnCreateStartTime)
))
}
}

六、多进程场景处理

/**
* 多进程 App 的启动追踪
*
* 每个进程独立运行,但需要共享 sessionId 和全局计数器。
* 使用 ContentProvider 跨进程共享状态。
*/
class MultiProcessStartupTracker(private val context: Context) {

private val authority = "${context.packageName}.tracking.state"

/**
* 生成或获取全局 sessionId
* 主进程生成 sessionId,子进程从 ContentProvider 读取
*/
fun getSessionId(): String {
val uri = Uri.parse("content://$authority/session_id")
return try {
val cursor = context.contentResolver.query(uri, null, null, null, null)
cursor?.use {
if (it.moveToFirst()) it.getString(0) else createNewSessionId()
} ?: createNewSessionId()
} catch (e: Exception) {
createNewSessionId()
}
}

private fun createNewSessionId(): String {
val sessionId = UUID.randomUUID().toString()
val uri = Uri.parse("content://$authority/session_id")
try {
val values = ContentValues().apply { put("session_id", sessionId) }
context.contentResolver.insert(uri, values)
} catch (_: Exception) {}
return sessionId
}

/**
* 全局前后台状态
* 只有当所有进程都退到后台,才上报 app_background
*/
fun reportProcessBackground(processName: String) {
val uri = Uri.parse("content://$authority/background_report")
val values = ContentValues().apply { put("process", processName) }
context.contentResolver.insert(uri, values)
}
}

七、架构流程图

              ┌──────────────────────────────┐
│ Zygote fork → 新进程 │
└──────────────┬───────────────┘

┌──────────────▼───────────────┐
│ TrackingInitProvider │
│ onCreate() (最早初始化) │
│ → 记录 processStartTime │
│ → registerActivityLifecycleCB │
└──────────────┬───────────────┘

┌──────────────▼───────────────┐
│ Application.onCreate() │
│ → 记录 appInitStartTime │
│ → 初始化异常退出检测器 │
└──────────────┬───────────────┘

┌─────────────────┼─────────────────┐
│ │ │
┌──────────▼─────┐ ┌────────▼──────┐ ┌───────▼──────┐
│ foregroundCnt │ │ foregroundCnt │ │ foregroundCnt│
│ == 0 → 冷启动 │ │ == 0 && │ │ == 0 && │
│ 首次 Activity │ │ lastTimestamp│ │ 从后台返回 │
│ onCreate │ │ → 温启动 │ │ → 热启动 │
└────────────────┘ └───────────────┘ └──────────────┘
│ │ │
└─────────────────┼─────────────────┘

┌──────────────▼───────────────┐
│ trackAppLaunch() │
│ ├── launch_type │
│ ├── launch_source │
│ ├── session_id │
│ └── launch_duration │
└──────────────┬───────────────┘

┌──────────────▼───────────────┐
│ Activity 生命周期变化 │
│ foregroundCount 维护 │
└──────────────┬───────────────┘

┌─────────────────┼─────────────────┐
│ │ │
┌──────────▼─────┐ ┌────────▼──────┐ ┌───────▼──────┐
│ count 0→1 │ │ count 1→0 │ │ isFinishing │
│ app_launch │ │ app_background │ │ app_exit │
│ (热启动) │ │ (进入后台) │ │ (主动退出) │
└────────────────┘ └───────────────┘ └──────────────┘

八、神策 / Firebase 集成示例

// 神策
SensorsDataAPI.sharedInstance().track("$AppStart", JSONObject().apply {
put("$is_first_time", isFirstLaunch)
put("$resume_from_background", !isFirstLaunch)
})

// Firebase
FirebaseAnalytics.getInstance(context).apply {
setDefaultEventParameters(Bundle().apply {
putString("session_id", sessionId)
})
logEvent(FirebaseAnalytics.Event.APP_OPEN, null)
}

九、ProGuard/R8 规则

-keep class com.example.tracking.launch.TrackingInitProvider { *; }
-keep class com.example.tracking.launch.AppLaunchTracker { *; }
-keep class com.example.tracking.launch.AbnormalExitDetector { *; }
-keep class com.example.tracking.launch.LaunchPerformanceTracker { *; }

面试常考问题

Q1:ProcessLifecycleOwner 如何感知所有 Activity 生命周期?

ProcessLifecycleOwner 内部注册了全局的 ActivityLifecycleCallbacks,通过一个 foregroundActivityCount 计数器跟踪前台 Activity 数量。核心逻辑:(1)onActivityStarted 时计数器 +1,若从 0→1 则判定 App 进入前台;(2)onActivityStopped 时计数器 -1,若从 1→0 则通过 Handler 发送一个 700ms 延迟消息;(3)如果 700ms 内没有新的 Activity onStart(即计数器重新从 0→1),则确认 App 已退到后台,触发 Lifecycle.Event.ON_STOP。这个 700ms 延迟是为了防止 Activity 切换(A.finish + B.start)期间的短暂计数器归零导致的误判。源码位于 androidx.lifecycle.ProcessLifecycleOwner.java

Q2:kill 进程和用户按 Home 键如何区分?

关键矛盾:进程被 kill 时不执行任何生命周期回调。区分策略:(1)在每次 onActivityStopped(退到后台)和 onActivityDestroyed(主动退出)时,使用 SharedPreferences/MMKV 持久化状态标记和时间戳;(2)下次冷启动时(Application.onCreate 或 ContentProvider.onCreate),检查持久化标记——如果上次记录的 state 是「foreground」或「background」,说明进程被异常终止(kill);(3)如果 state 是「destroyed」,说明是正常的主动退出(用户按 Back 键退出最后一个 Activity);(4)按 Home 键会正常触发 onStop,标记为「background」,后续可区分是自然回到前台(正常)还是被 kill 后冷启动(异常)。

Q3:如何区分 launcher 启动和 deeplink 启动?

通过首个 Activity 的 Intent 属性判断:(1)Launcher 启动:Intent.action == ACTION_MAIN && Intent.hasCategory(CATEGORY_LAUNCHER);(2)Deep Link 启动:Intent.action == ACTION_VIEW && Intent.data != null;(3)通知栏点击:检查 Intent.extras 中是否有自定义 key(如 notification_id),或通过 PendingIntent 携带的标识;(4)最近任务列表:Intent.flags & FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY != 0;(5)分享:Intent.action == ACTION_SEND。更精确的判断可以通过 Activity.getReferrer()(API 22+)获取启动来源包名,或使用 Analytics SDK 的 referrer 参数。

Q4:如何准确测量 App 启动耗时(TTID / TTFD)?

Google Vitals 定义了两种启动时间指标:(1)TTID(Time To Initial Display):从进程创建到首个 Activity 首帧绘制的时间。测量方法:在 ContentProvider.onCreate()(最早执行点)记录起始时间,在首个 Activity 的 DecorView.getViewTreeObserver().addOnDrawListener 回调中记录结束时间;(2)TTFD(Time To Full Display):从进程创建到首个 Activity 完成所有异步数据加载和布局的时间。通过 Activity.reportFullyDrawn() 手动标记全量显示时间。Android 系统在 adb shell am start -W 中报告的 TotalTime 就是 TTID。注意区分:System.currentTimeMillis() 受系统时间调整影响,应使用 SystemClock.elapsedRealtime()

Q5:多进程 App 中,每个进程都会触发 app_launch 事件吗?

是的。每个 Android 进程都有独立的 Application 和 ActivityLifecycleCallbacks 实例。如果不做特殊处理,主进程和子进程(如 WebView 进程、推送进程、图片加载进程)都会各自上报 app_launch 事件,导致数据重复。解决方案:(1)通过 ActivityManager.getRunningAppProcesses()getProcessName() 判断当前进程名,只在主进程上报启动事件;(2)使用 ContentProvider 共享全局 sessionId 和前后台状态;(3)服务端通过 sessionId + processName 做去重和关联;(4)注意 android:process 属性声明的多进程,以及 WebView 可能独立启动的 :webview_sandboxed_process 进程。

AOSP 源码路径:ActivityThread.main() 方法中触发 Application.onCreate()frameworks/base/core/java/android/app/ActivityThread.java),ActivityManagerService 管理进程生命周期(frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java),ProcessRecord 跟踪进程状态(frameworks/base/services/core/java/com/android/server/am/ProcessRecord.java)。

打赏
  • 微信
  • 支付宝

评论