免费获取学习方案
ARTICLE DETAIL

资讯详情

深耕编程基础知识与建站技术分享的一线实战洞察。

Android 性能优化:02.启动时长优化

Android 性能优化:02.启动时长优化 一、启动时长的重要性启动是用户与 App 的第一次交互也是流失率最高的路径之一。业界的共识数据冷启动每多 1 秒用户的等待焦虑呈非线性上升Google 官方建议冷启动首帧TTID控制在 500ms 以内为优秀2s 以上即为慢启动。Google Play Vitals 对启动有明确的坏行为阈值会影响应用在商店的推荐权重慢冷启动≥ 5s慢温启动≥ 2s慢热启动≥ 1s启动过程集中了 App 架构中最多的历史债务SDK 初始化堆叠、主线程 IO、布局臃肿是投入产出比最高的优化场景。优化方法先测量、再优化、后监控不要在没数据的情况下动手。二、核心概念2.1 三种启动类型这是面试和优化工作的基础概念三者耗时差异巨大测量时必须区分类型前提条件系统行为典型耗时冷启动进程不存在开机后首次、被系统回收、被用户杀掉创建进程 → 创建 Application → 创建并绘制首个 Activity最慢优化主战场温启动进程存活但 Activity 已被销毁如按返回键退出后再次进入复用进程重新创建 Activity中等热启动进程与 Activity 都存活按 Home 键后切回仅将 Activity 带到前台onRestart→onStart→onResume最快常见误区很多优化成果其实是拿热启动数据冒充冷启动数据。规范的冷启动测量姿势是先adb shell am force-stop pkg或静置等待系统回收。2.2 两个关键指标TTID 与 TTFDTTIDTime To Initial Display初始显示时间从启动到第一帧绘制完成的时间。由系统自动统计logcat 中的Displayed日志和am start -W的 TotalTime 都是它。它只代表屏幕上出现了东西不代表页面可用。TTFDTime To Full Display完全显示时间从启动到首屏内容完整加载渲染如列表数据填充、首图加载完的时间。系统不知道你的业务何时算完成需要开发者手动调用Activity.reportFullyDrawn()上报。优化实践中用 TTID 做系统侧基准用 TTFD 做用户体验基准两者缺一不可。只优化 TTID 很容易出现白屏快、内容慢的假优化。2.3 冷启动全链路源码视角点击桌面图标到首帧上屏完整链路如下用户点击图标 └─ Launcher 进程 → startActivity └─ AMS(ATMS) 校验、创建 ActivityRecord、任务栈管理 └─ 进程不存在 → Zygote.fork() 孵化新进程 └─ 新进程入口ActivityThread.main() ├─ 初始化主线程 Looper └─ attach() → AMS.attachApplication() └─ bindApplication绑定进程 ├─ LoadedApk.makeApplication() 创建 Application ├─ Application.attachBaseContext() ← 应用代码最早执行点 ├─ installContentProviders() ← 所有 ContentProvider 在此创建 └─ Application.onCreate() ← SDK 初始化重灾区 └─若有后台进程组件则再次循环以上流程 → 多进程初始化问题 └─ launchActivity └─ Activity 生命周期onCreate → onStart → onResume ├─ setContentView解析 XML、反射/LayoutInflater 实例化 View 树 ├─ Window 创建、DecorView 挂载 └─ measure / layout / draw → Vsync → 首帧上屏TTID上图链路的各步骤都是一个可测量、可优化的耗时点后文的所有手段都是在这条链路的某一段上做文章。2.4 冷启动耗时的四大构成进程创建与类加载fork、类校验、dex 解释执行/JIT。未做编译优化的应用首次启动大量代码走解释执行这是安装后第一次启动特别慢的根因。Application 阶段ContentProvider 创建注意每个 Provider 都是串行初始化且三方 SDK 常偷偷塞 Provider、Application.onCreate 里的 SDK 同步初始化。Activity 与布局阶段XML 解析、View 树构建、主题与资源加载、首帧绘制。数据等待阶段首屏接口、本地缓存读取、图片解码影响的是 TTFD。2.5 必须掌握的技术名词Zygote所有应用进程的母体通过 fork 复用预加载的框架类与资源降低进程创建成本。ART 编译策略JIT/AOTAndroid 7.0 采用混合编译。安装时只编译热点代码首次启动大量走解释执行 JIT热点积累后再 AOT。这正是 Baseline Profile 要解决的问题。Baseline Profile基准配置文件把启动和关键路径会用到哪些类和方法提前告诉系统安装/闲时即完成 AOT 预编译官方数据可带来约 30% 的启动速度提升是目前性价比最高的一招后面单独展开。Cloud ProfileGoogle Play 聚合大量真实设备生成的配置文件随安装分发与本地 Baseline Profile 叠加生效。ContentProvider 自动初始化任何在 manifest 注册 Provider 的 SDKWorkManager、Lifecycle、不少三方 SDK都会在 Application.onCreate之前被串行创建是不可见的耗时大户。多进程初始化App 的每个进程启动都会执行一遍 Application 生命周期只按主进程设计初始化逻辑会造成浪费甚至 crash。三、测量先把耗时变成数字3.1 adb 命令最快的线下粗测# 先确保冷启动环境adb shell am force-stop com.example.app# 启动并输出耗时adb shell am start-W-ncom.example.app/.MainActivity输出解读ThisTime: 812 # 最后一个 Activity 启动到首帧的耗时 TotalTime: 812 # 新应用启动总耗时含进程创建冷启动看这个 WaitTime: 855 # 系统返回总耗时含前一个 Activity 的 pause一般 ≥ TotalTime注意单次测量波动大至少测 10 次取中位数且要固定机型、清后台、关闭网络代理等变量。3.2 logcat 系统日志adb logcat|grep-EDisplayed|Fully drawnDisplayed com.example.app/.MainActivity: 812ms→ TTIDFully drawn ...需在代码中调用reportFullyDrawn()→ TTFD// 在首屏数据渲染完成的时机上报如列表 Adapter 数据填充后、首帧回调中lifecycleScope.launch{valdatarepository.loadHomeData()adapter.submitList(data)recyclerView.doOnPreDraw{reportFullyDrawn()}}3.3 代码内打点自定义指标classMyApp:Application(){overridefunattachBaseContext(base:Context){super.attachBaseContext(base)// API 24 可直接拿到真实的进程启动时刻比自己打点更准LaunchTimer.processStartMsProcess.getStartElapsedRealtime()LaunchTimer.mark(attachBaseContext)}overridefunonCreate(){super.onCreate()LaunchTimer.mark(app_onCreate_end)}}// Activity 首帧window.decorView.doOnPreDraw{LaunchTimer.mark(first_frame)LaunchTimer.dump()// 输出各阶段耗时}这套打点稍作封装结合 BuildConfig 开关 上报通道就是线上监控的雏形。3.4 Perfetto / Systrace定位耗时细节的核心武器Android 10 推荐 Perfetto。录制启动 trace# 方式一命令行推荐用 perfetto.dev 网页的 Record 功能生成配置adb shell perfetto-o/data/misc/perfetto-traces/startup.pftrace-t20s\am wm ss view res dalvik sched freq idle binder_driver adb pull /data/misc/perfetto-traces/startup.pftrace# 拖到 https://ui.perfetto.dev 分析在代码中打自定义 trace 点让业务逻辑出现在 trace 里importandroidx.tracing.Trace Trace.beginSection(initAdSdk)AdSdk.init(this)Trace.endSection()分析时重点看bindApplication、activityStart、Choreographer#doFrame、主线程的 Runnable/IO 段、锁等待monitor contention。一条原则主线程在启动链路上出现的每一毫秒都要问一句它必须现在做吗。3.5 Macrobenchmark可复现、可进 CI 的基准测试Jetpack Macrobenchmark 是目前最规范的启动测量方案可自动清编译状态、循环测量、输出统计分布并能在 CI 里做回归RunWith(AndroidJUnit4::class)classStartupBenchmark{get:RulevalbenchmarkRuleMacrobenchmarkRule()TestfuncoldStartup()benchmarkRule.measureRepeated(packageNamecom.example.app,metricslistOf(StartupTimingMetric()),compilationModeCompilationMode.Partial(),// 贴近真实用户带 Baseline Profile 的编译状态startupModeStartupMode.COLD,iterations10){pressHome()startActivityAndWait()}}CompilationMode的选择很有讲究None()模拟无编译优化最差情况、Partial()模拟真实用户Baseline Profile 生效、Full()全量 AOT最好情况。对比 None 和 Partial 的结果就能量化 Baseline Profile 的收益。3.6 线上监控Android Vitals零成本Play Console 直接查看冷/温/热启动的分布与慢启动会话占比按机型、系统版本拆分。自研/三方 APM基于 3.3 的打点上报 TTID/TTFD核心看P50 / P90 分位值而非平均值平均值会被少量极端样本带偏高端机优化好了不代表低端机没问题分机型看。开源方案参考Matrix微信、Booster滴滴含字节码插桩能力。四、优化步骤总览方法论Step 1 建指标定义 TTID / TTFD选定基准机型主力机型 一款低端机 Step 2 测基线Macrobenchmark Perfetto 记录优化前数据输出 trace 火焰图 Step 3 拆链路把启动耗时按「进程创建 / Application / Activity / 数据」四段拆解归因 Step 4 逐段优化先收益大的通常 Application 初始化 Baseline Profile Step 5 对比验证同条件复测确认 P50/P90 下降且无功能回归 Step 6 防劣化benchmark 进 CI 线上分位值告警优化次序经验法则测量工具链搭建 → Baseline Profile改动小收益大→ Application 初始化治理 → 首屏布局与数据 → 进阶新技术。五、分阶段优化手段实战清单5.1 编译与代码层面优先 Baseline Profile这是当前官方最推荐、投入产出比最高的手段。原理把启动路径的热点类和方法打进配置文件系统在安装后/空闲时直接 AOT 编译绕开解释执行和 JIT 预热期。接入方式AGP 8.x / Baseline Profile Gradle 插件// app/build.gradle.ktsbaselineProfile{// 自动生成基准配置文件的开关automaticGenerationDuringBuildtrue}新建baselineprofile模块Android Studio 有现成模板New Module → Baseline Profile Generator编写生成用例RunWith(AndroidJUnit4::class)classStartupBaselineGenerator{get:RulevalbaselineProfileRuleBaselineProfileRule()Testfungenerate()baselineProfileRule.collect(packageNamecom.example.app){pressHome()startActivityAndWait()// 可以顺便把登录后首页、核心二级页也走一遍收益更大}}执行./gradlew :app:generateReleaseBaselineProfile生成的baseline-prof.txt会打进 APK/AAB。随后用 Macrobenchmark 对比CompilationMode.None()与Partial()验证收益。配套动作开启R8 全模式 代码/资源缩减isMinifyEnabled/isShrinkResources减小 dex 体积就是减小类加载和校验成本。老项目若还在 Multidex升级 minSdk 到 21 直接去掉 MultiDex否则主 dex 裁剪和 dex 布局优化dex layout也值得做ReDex / Booster 都有成熟方案原理是把启动用到的类排到主 dex 前部减少 IO 页缺失。5.2 Application 阶段初始化治理多数 App 的最大收益点(1) 统计并消灭 ContentProvider 自动初始化manifest 里每个 Provider 都在 Application.onCreate 之前串行创建。先在 trace 里数一遍有哪些 ProviderinstallContentProviders段处理思路三方 SDK 若只是为了初始化而注册 Provider用tools:noderemove移除改为手动初始化providerandroid:namecom.some.sdk.SdkInitProviderandroid:authorities${applicationId}.some-sdktools:noderemove/自己模块的初始化统一收敛到Jetpack App Startup它把所有 initializer 合并进一个InitializationProviderclassAdSdkInitializer:InitializerUnit{overridefuncreate(context:Context){AdSdk.init(context)}overridefundependencies():ListClassoutInitializer*listOf(ConfigInitializer::class.java)// 声明依赖自动拓扑排序}(2) 初始化任务分级治理把 onCreate 里的所有任务按谁在什么时候需要它分三级级别定义策略典型例子P0 必需首帧绘制前必须完成保留主线程同步执行只留极少量崩溃监控、进程名判断、基础配置P1 首屏后首屏可用前需要但不挡首帧IdleHandler/ 首帧后延迟队列埋点上报通道、AB 实验下发P2 懒加载用到才需要使用时初始化双重检查锁/单例广告 SDK、推送、支付、WebView 池// P1 示例主线程空闲时才执行不抢首帧Looper.myQueue().addIdleHandler{TrackerSdk.init(context)false// 返回 false 执行一次即移除}(3) 异步与并行P0 内可并行的任务丢进启动线程池注意线程池别贪大启动期 CPU 本来就在抢且子线程任务要考虑 Application 持有生命周期避免 Activity 回调空指针。进阶方式是引入启动任务编排框架DAG 有向无环图参考阿里 Alpha / 自研 AnchorTask 思路任务声明依赖关系与执行线程框架统一调度既解决先后顺序靠注释维护的混乱也方便输出各任务耗时报表。Kotlin 的by lazy只是访问时初始化如果首次访问仍在主线程关键路径上一样是耗时。(4) 消灭主线程 IO 与锁SharedPreferences在启动期高频读写是经典坑单文件过大首次加载全量解析、跨进程 MODE_MULTI_PROCESS 已废弃。方案拆分文件 首屏只读必需项或迁移MMKV / DataStore。启动期禁做网络请求、数据库重操作确有需求走异步。trace 里搜monitor contention看主线程锁等待常见来源是单例的双重检查锁和 OkHttp 内部锁。(5) 多进程适配valprocessNameApplication.getProcessName()// API 28低版本读 /proc/self/cmdlineif(processNamepackageName){// 只有主进程才做全量初始化}推送进程、WebView 进程、下载进程各自只做自己需要的初始化能直接砍掉重复成本。5.3 首屏 Activity 阶段(1) 布局减载减少层级与过度嵌套ConstraintLayout/FrameLayout打平层级能用merge的地方别多套一层。非首屏 View 用ViewStub延迟 inflate。首页布局重的场景用AsyncLayoutInflater异步构建 View 树注意回调时机与主题兼容。新项目直接 Compose配合 Baseline Profile 的收益更明显Compose 官方库自带 profile 规则。(2) 启动预览页视觉体验优化秒开的障眼法给启动 Activity 配置带品牌图的启动主题系统窗管在应用内容 ready 前先绘制 windowBackgroundstylenameTheme.App.LaunchparentTheme.Material.NoActionBaritem nameandroid:windowBackgrounddrawable/launch_screen/item/styleoverridefunonCreate(savedInstanceState:Bundle?){setTheme(R.style.Theme_App)// setContentView 之前换回真实主题super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)}(3) SplashScreen APIAndroid 12 必做适配Android 12 起系统强制展示开屏页接入androidx.core:core-splashscreen向下兼容overridefunonCreate(savedInstanceState:Bundle?){valsplashinstallSplashScreen()// 必须在 super.onCreate 之前super.onCreate(savedInstanceState)// 关键让开屏页停留到数据就绪避免开屏 → 白屏 → 内容的三段跳varreadyfalsesplash.setKeepOnScreenCondition{!ready}viewModel.uiState.onEach{readyit.loaded}.launchIn(lifecycleScope)}setKeepOnScreenCondition是把 TTID 和 TTFD 平滑衔接起来的官方方案强烈推荐。(4) 首帧之后的事别提前onCreate 里只放首帧必需逻辑onWindowFocusChanged、首帧回调之后再触发二阶段任务预加载下一级页面、初始化 WebView 池等。5.4 数据与网络层面TTFD 优化首屏数据预取 缓存直出冷启动先渲染本地缓存快照接口回来后增量刷新TTFD 立即可用。首屏接口前置在闪屏阶段就发出首屏请求甚至 Application 阶段发起、Activity 消费结果让网络和 UI 初始化并行。减少首屏串行请求链能合并的接口合并图片用缩略图 渐进式加载。广告开屏场景广告请求与应用初始化并行但要做好超时熔断别让广告 SDK 把首屏拖住超时直接进首页。5.5 进阶手段头部 App 玩法按需了解类加载优化基于 Baseline Profile 的 dex 布局重排Redex Interdex / Booster dextral把启动类聚合到主 dex 前部减少 mmap 页缺失 IO。启动期 GC 抑制 / 线程优先级调整短暂提升主线程优先级、延后非关键线程收益因机而异需 A/B 验证。资源加载优化预加载首屏 drawable、字体懒加载Downloadable Fonts 注意首屏阻塞。WebView 预热池首屏含 WebView 的 App 常用在空闲期预创建 WebView 实例注意多进程与内存开销。15/16KB 页大小适配新设备内存页增大后native 库对齐与 so 体积会影响加载耗时打包时留意 16KB 对齐支持。保活/热启动占比提升属于体验博弈手段合规前提下谨慎使用不展开。六、持续监控与防劣化CI 卡点Macrobenchmark 接入 CI核心指标TTID P50/P90回归超过阈值如 10%直接报警或阻断合入。线上分位值告警看 P90 而不只是均值分机型/系统版本/版本号维度对比。新增 SDK 评审机制任何 SDK 接入前回答三个问题——是否在启动链路上初始化是否注册了 ContentProvider能否懒加载版本复盘每个大版本出一页启动数据对比固化到团队 Wiki。七、常见误区清单❌ 用热启动/温启动数据汇报优化成果❌ 只测一次、只在旗舰机上测❌ 在 Debug 包上测性能Debug 关闭了 JIT/AOT 优化和 R8数据无意义一律用 release 包 关闭 Instant Run❌ 只看 TTID 不看 TTFD搞出白屏秒出、内容三秒的假快❌ 不看 trace 凭直觉优化把时间花在收益最小的环节❌ 接了优化却不上 Baseline Profile白白放弃 30% 的官方红利八、总结启动优化 链路fork → Application → Activity → 首帧 → 数据就绪× 尺子TTID / TTFD× 三板斧Baseline Profile、初始化治理、首屏减载× 一套护栏CI benchmark 线上分位值告警。参考资料Android 官方文档App startup timedeveloper.android.com/topic/performance/vitals/launch-timeAndroid 官方文档Baseline Profiles 与 MacrobenchmarkAndroid Vitalsdeveloper.android.com/topic/performance/vitalsPerfetto 官方文档perfetto.dev《Android 移动性能实战》腾讯 SNG 团队、支付宝/手淘/抖音启动优化技术分享
返回列表