Administrator
发布于 2026-08-26 / 23 阅读
0
0

系列文章《Android 工程师的 100 篇进阶笔记》第 87 篇:Android 启动框架:冷启动链路的完整剖析与优化

前言

App 启动时间是用户第一感受。打开 1 秒就见到内容 = 留下用户;打开 5 秒还在看白色启动屏 = 一秒流失。从工程角度,启动时间牵扯到进程创建、Application 初始化、Activity onCreate、首次 measure/draw/layout。本篇拆解整个启动链路,并给出"1 秒冷启动"的落地方法。

一、Android 启动的三种类型

类型

含义

业界目标

冷启动

进程不存在,重新 fork

< 2 秒

温启动

进程存在,Activity 重建

< 1 秒

热启动

Activity 在栈里,恢复

< 0.5 秒

冷启动包含进程创建,最慢;也是工程师要重点优化的。

二、冷启动链路的 7 个阶段

点击图标
  │
  ▼
① 系统调度(< 50ms)            Zygote fork 进程
  │
  ▼
② Application 创建(50-300ms)   Application.attachBaseContext
  │
  ▼
③ Application.onCreate(最关键)  业务初始化
  │
  ▼
④ 第一个 Activity.onCreate       SplashActivity / MainActivity
  │
  ▼
⑤ Activity.onStart / onResume    装载 ViewModel
  │
  ▼
⑥ 首次 measure/layout/draw      渲染第一帧
  │
  ▼
⑦ 用户可见内容

三、测量启动时间

3.1 systrace / Perfetto

# 在开发者选项启用 systrace
adb shell setprop debug.atrace.trace_flags 1

# 启动 app,30 秒后拉 trace
adb shell atrace --async_stop -o /sdcard/launch.html
adb pull /sdcard/launch.html .

看 am_proc_start、am_create_task、am_create_activity 等关键节点。

3.2 Android Vitals 启动时间

Android 9+ 在开发者选项 / 桌面 → 设置 → 开发者 → 显示 SurfaceFlinger:

adb shell am start -W com.example.app/.MainActivity

输出:

Status: ok
Activity: com.example.app/.MainActivity
ThisTime: 1923    // 从 click 到第一帧
TotalTime: 2350   // 含 Application
WaitTime: 110

3.3 自定义启动测量

class App : Application() {
    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        // T0: 进程开始
    }

    override fun onCreate() {
        super.onCreate()
        // T1: Application 初始化完
        PerformanceTracer.mark("app_init_end")

        registerActivityLifecycleCallbacks(object : ActivityLifecycleCallbacks {
            override fun onActivityResumed(activity: Activity) {
                if (activity is MainActivity) {
                    // T2: 第一帧渲染
                    PerformanceTracer.mark("first_frame")
                }
            }
        })
    }
}

object PerformanceTracer {
    private val marks = ConcurrentHashMap<String, Long>()
    fun mark(key: String) {
        marks[key] = SystemClock.uptimeMillis()
        log("MARK $key at ${marks[key]}ms")
    }
    fun duration(from: String, to: String) =
        marks[to]!! - marks[from]!!
}

3.4 Baseline Profiles + Macrobenchmark 准出

@RunWith(AndroidJUnit4::class)
class StartupBenchmark {
    @get:Rule
    val benchmarkRule = MacrobenchmarkRule()

    @Test
    fun coldStartup() {
        benchmarkRule.measureRepeated(
            packageName = "com.example.app",
            metrics = listOf(StartupTimingMetric()),
            startupMode = StartupMode.COLD,
            compilationMode = CompilationMode.DEFAULT,
            iterations = 5,
            setupBlock = { pressHome() }
        ) {
            startActivityAndWait(Intent(Intent.ACTION_MAIN).setClassName("com.example.app", "MainActivity"))
        }
    }
}

生产级团队发版前必跑这个测试。

四、各阶段的优化招

4.1 Application.onCreate 中能推迟的初始化

class App : Application() {
    override fun onCreate() {
        super.onCreate()
        // 必须同步做的:Crash 上报、Push 中必装的(如果有)
        CrashReport.install(this)

        // 启动器(Initializer)模式
        StartupManager.register(this)
            .startupAsync("Bugly") { /* ... */ }
            .startupAsync("JPush") { /* ... */ }
            .startupAsync("UMeng") { /* ... */ }
            .startupAsync("UserCenter") { /* ... */ }
            .startupOnFirstShow("Ads") { /* ... */ }
            .startupOnRenderEnd("DataSync") { /* ... */ }
    }
}

启动器(App Startup)框架是 Google 的官方方案,但很多公司在其之上又做了业务封装。

4.2 业务初始化的同步 vs 异步

模块

启动方式

示例

必须

同步

Crash 上报、Realm 加密 key、推送 channel

重要

异步延迟

推送 SDK、统计 SDK、运营配置

不紧急

IdleHandler 后

钱包 SDK、AI 模型加载、广告 SDK

首次展示

显示后

用户中心、客服 IM

渲染完成后

Choreographer.end

数据预拉、长连接

4.3 View 扁平化

<!-- 错误:嵌套 5 层 LinearLayout -->
<LinearLayout>
    <LinearLayout>
        <LinearLayout>
            <LinearLayout>
                <LinearLayout>
                    <TextView/>
                </LinearLayout>
            </LinearLayout>
        </LinearLayout>
    </LinearLayout>
</LinearLayout>

<!-- 正确:ConstraintLayout 一层 -->
<ConstraintLayout>
    <TextView app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />
</ConstraintLayout>

Android 渲染按 view tree 深度 O(n),层次深耗时长。

4.4 主页布局预加载

主页 drawable 可以预先 inflate:

private object MainPagePreloader {
    fun preload(context: Context) {
        val start = SystemClock.uptimeMillis()
        LayoutInflater.from(context).inflate(R.layout.activity_main, null)
        PerformanceTracer.log("mainPreload cost ${SystemClock.uptimeMillis() - start}ms")
    }
}

// 在 IdleHandler 触发
Looper.myQueue().addIdleHandler {
    MainPagePreloader.preload(App.context)
    false
}

4.5 Splash 主题优化

<!-- res/values/themes.xml -->
<style name="Theme.Launcher" parent="Theme.Material3.DayNight">
    <item name="android:windowBackground">@drawable/launch_image</item>
</style>

windowBackground 在冷启动时立即显示,避免黑屏/白屏。

注意:不能放复杂 drawable,否则 inflate 拖慢启动。

4.6 Splash Screen API

implementation("androidx.core:core-splashscreen:1.0.1")
class SplashActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        val splashScreen = installSplashScreen()
        splashScreen.setKeepOnScreenCondition { !isReady }
        super.onCreate(savedInstanceState)
        // ...
    }
}

Android 12+ 推荐用 Splash Screen API 替代旧的 splash theme。

五、Baseline Profiles:启动加速的"杀手锏"

5.1 原理

Baseline Profiles 告诉 AOT 编译器预编译哪些方法,跳过 JIT 阶段。开启后,启动时间能省 30%-40%。

5.2 生成

@RunWith(AndroidJUnit4::class)
class BaselineProfileGenerator {
    @get:Rule
    val baselineProfileRule = BaselineProfileRule()

    @Test
    fun startupBaselineProfile() {
        baselineProfileRule.collect(
            packageName = "com.example.app",
            stableIterations = 3,
            maxStableIterations = 5,
            stableIterationsTimeout = Duration.ofSeconds(30)
        ) {
            // 让 App 走完启动路径
            startActivityAndWait()
            device.waitForIdle()
        }
    }
}
./gradlew :benchmark:pixel6Api31BenchmarkAndroidTest
# 生成 baseline-prof.txt

5.3 应用

android {
    buildTypes {
        release {
            baselineProfile {
                startup = true
            }
        }
    }
}

每发版前重新生成一次,让基线贴合最新代码。

六、启动期间的资源分配

启动前 100ms:CPU 密集(Flash 解析)
启动 100-500ms:ClassLoader、加载资源
启动 500-1500ms:业务初始化
启动 1500ms+:Activity 渲染

定向加速:

阶段

优化

0-100ms

减少 APK 大小

100-500ms

R8 / App Bundle / 资源混淆

500-1500ms

启动器、异步初始化

1500ms+

Baseline Profiles / 主页预加载

七、多进程与启动时间

如果你用了 :push 独立进程:

android {
    defaultConfig {
        // 多进程 push
        manifestPlaceholders["PUSH_PROCESS"] = ":push"
    }
}

主进程冷启完成后,push 进程还未必要加载——直接用独立进程初始化,主进程不阻塞。

八、启动监控与告警

线上监控启动时间的曲线:

object StartupTracker {
    fun track(timeMs: Long, version: String, model: String) {
        val bucket = StartupBucket.get(timeMs)  // bucket = < 1s, < 2s, >= 2s
        FirebasePerformance.getInstance().newTrace("startup_$version")
            .putMetric("duration_ms", timeMs)
            .start().stop()
        // 上报到自建 APM
    }
}

监控:

  • 启动时间 P50 / P95

  • 按机型、版本、渠道分桶

  • 灰度期间对照对照组

九、常见反模式(踩过的坑)

9.1 子线程阻塞主线程

// 错误:在 attachBaseContext 里读 SharedPreferences
override fun attachBaseContext(base: Context?) {
    super.attachBaseContext(base)
    val token = base!!.getSharedPreferences("sp", MODE_PRIVATE).getString("token", "")
    // 这种 sync 读取可能在冷启动时阻塞
}

// 正确:使用 Glide 风格的 defer
override fun attachBaseContext(base: Context?) {
    super.attachBaseContext(base)
    preloadQueue.postDelayed({
        sp.getString("token", "")
    }, 100)
}

9.2 Application 里做磁盘 IO

// 错误
override fun onCreate() {
    super.onCreate()
    db.copyDatabaseFromAssets()   // 100ms
    loadAllConfigFromLocal()        // 50ms
}

// 正确:分阶段 / 后台线程

9.3 过多的 ContentProvider

每个 ContentProvider 都是 onCreate 一次,几十个 ContentProvider 在启动期并发执行会被放大。Manifest 中合并或减少动态声明。

十、启动优化效果对比

阶段动作

启动时长影响

删除无用 ContentProvider

-100ms

R8 + App Bundle

-300ms

启动器模式

-200ms

主页预加载

-150ms

Baseline Profiles

-400ms

工具链升级(K2)

-300ms

优化累加

起始

最终

1K 行 APK + 简单启动

4-6 秒

1.5-2.5 秒

小结

启动优化不是一招制胜,而是把"启动期内能做的所有事"重新组织一次:从进程创建时机、初始化顺序、视图渲染、Baseline 编译,每一段都有改进空间。1.5 秒冷启动 是商业 App 的当前标准。

下一篇进入卡顿监控专题——Choreographer 与 FPS 监测,从掉帧到线上监控的完整链路。

参考阅读

  • Android Vitals:App Startup Time

  • Baseline Profile 官方文档

  • Macrobenchmark 库使用指南


评论