在应用启动期间,您的应用会给用户留下第一印象。应用启动必须快速加载并显示用户使用您的应用所需的信息。如果您的应用启动时间过长,用户可能会因为等待时间过长而退出您的应用。
我们建议您使用 Macrobenchmark 库来测量启动时间。该库提供概述和详细的系统跟踪,以便准确了解启动期间发生的情况。
系统跟踪提供有关设备上发生情况的有用信息,这有助于您了解应用在启动期间的操作以及识别潜在的优化领域。
要分析您的应用启动情况,请执行以下操作
- 设置用于记录应用启动跟踪的环境。
- 了解系统跟踪。
- 使用Android Studio Profilers或Perfetto浏览跟踪报告。
分析和优化启动步骤
应用通常需要在启动期间加载对最终用户至关重要的特定资源。非必要资源可以等到启动完成后再加载。
为了权衡性能,请考虑以下几点
使用 Macrobenchmark 库来测量每个操作所需的时间,并识别完成时间长的块。
确认资源密集型操作对于应用启动至关重要。如果操作可以等到应用完全绘制后再执行,则可以帮助最大限度地减少启动时的资源限制。
确保您期望此操作在应用启动时运行。通常,不必要的操作可以从遗留代码或第三方库中调用。
如果可能,将长时间运行的操作移至后台。后台进程仍会影响启动期间的 CPU 使用率。
在您完全调查操作后,您可以决定加载时间与将其包含在应用启动中的必要性之间的权衡。记住,在更改应用的工作流程时,要考虑可能出现的回归或重大更改。
优化并重新测量,直到您对应用的启动时间满意为止。有关更多信息,请参阅使用指标检测和诊断问题。
测量和分析主要操作所花费的时间
当您拥有完整的应用启动跟踪时,请查看跟踪并测量主要操作(如bindApplication
或activityStart
)所花费的时间。我们建议使用Perfetto或Android Studio Profilers来分析这些跟踪。
查看应用启动期间的总时间,以识别执行以下操作的任何操作
- 占用大量时间段并且可以优化的操作。性能的每一毫秒都很重要。例如,查找
Choreographer
绘制时间、布局膨胀时间、库加载时间、Binder
事务或资源加载时间。一般来说,请查看所有耗时超过 20 毫秒的操作。 - 阻塞主线程。有关更多信息,请参阅浏览 Systrace 报告。
- 不需要在启动期间运行。
- 可以等到第一帧绘制完成之后。
进一步调查每个跟踪以查找性能差距。
识别主线程上的昂贵操作
最佳实践是将文件 I/O 和网络访问等昂贵操作保持在主线程之外。这在应用启动期间同样重要,因为主线程上的昂贵操作会使应用无响应并延迟其他关键操作。StrictMode.ThreadPolicy
可以帮助识别主线程上发生昂贵操作的情况。最好在调试版本中启用StrictMode
以尽早识别问题,如下例所示
Kotlin
class MyApplication : Application() { override fun onCreate() { super.onCreate() ... if (BuildConfig.DEBUG) StrictMode.setThreadPolicy( StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build() ) ... } }
Java
public class MyApplication extends Application { @Override public void onCreate() { super.onCreate(); ... if(BuildConfig.DEBUG) { StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder() .detectAll() .penaltyDeath() .build() ); } ... } }
使用StrictMode.ThreadPolicy
可在所有调试版本上启用线程策略,并在检测到违反线程策略时使应用崩溃,这使得难以错过线程策略违规。
TTID 和 TTFD
要查看应用生成第一帧所需的时间,请测量初始显示时间(TTID)。但是,此指标并不一定反映用户可以开始与您的应用交互的时间。完全显示时间(TTFD) 指标在测量和优化拥有完全可用应用状态所需的代码路径方面更有用。
有关在应用 UI 完全绘制时报告策略的信息,请参阅提高启动计时精度。
针对 TTID 和 TTFD 进行优化,因为两者在其各自领域都很重要。较短的 TTID 有助于用户看到应用实际上正在启动。保持 TTFD 简短对于确保用户可以快速开始与应用交互非常重要。
分析整体线程状态
选择应用启动时间并查看整体线程切片。主线程需要始终保持响应。
诸如Android Studio Profiler和Perfetto之类的工具提供了主线程的详细概述以及每个阶段花费的时间。有关可视化 perfetto 跟踪的更多信息,请参阅Perfetto UI文档。
识别主线程睡眠状态的主要块
如果花费大量时间睡眠,则可能是您的应用主线程等待工作完成的结果。如果您有一个多线程应用,请识别主线程正在等待的线程,并考虑优化这些操作。确保没有不必要的锁争用导致关键路径延迟也是很有用的。
减少主线程阻塞和不可中断睡眠
查找主线程进入阻塞状态的每个实例。Perfetto 和 Studio Profiler 在线程状态时间线上用橙色指示器显示此情况。识别操作,探讨这些操作是否预期或可以避免,并在必要时进行优化。
与 IO 相关的可中断睡眠可能是改进的绝佳机会。即使是无关的应用,其他执行 IO 的进程也可能会与顶级应用正在执行的 IO 竞争。
提高启动时间
在您确定优化机会后,请探索可能的解决方案以帮助提高启动时间
- 延迟和异步加载内容以加快TTID。
- 最大限度地减少调用进行 Binder 调用的函数。如果不可避免,请确保通过缓存值而不是重复调用或将非阻塞工作移至后台线程来优化这些调用。
- 为了使您的应用启动看起来更快,您可以尽快向用户显示需要最小渲染的内容,直到屏幕其余部分加载完毕。
- 创建并添加启动配置文件到您的应用。
- 使用 Jetpack App Startup 库简化应用启动期间组件的初始化。
分析 UI 性能
应用启动包括启动画面和主页的加载时间。为了优化应用启动,请检查跟踪以了解 UI 绘制所需的时间。
限制初始化工作
某些帧的加载时间可能比其他帧长。这些被认为是应用的昂贵绘制。
要优化初始化,请执行以下操作
- 优先处理缓慢的布局传递并选择这些进行改进。
- 通过添加自定义跟踪事件来调查 Perfetto 的每个警告和 Systrace 的每个警报,以减少昂贵的绘制和延迟。
测量帧数据
有多种方法可以测量帧数据。五种主要的收集方法是
- 使用
dumpsys gfxinfo
进行本地收集:并非在 dumpsys 数据中观察到的所有帧都负责应用的缓慢渲染或对最终用户有任何影响。但是,这是在不同的发布周期中查看以了解性能一般趋势的好方法。要了解有关使用gfxinfo
和framestats
将 UI 性能测量集成到您的测试实践中的更多信息,请参阅Android 应用测试基础知识。 - 使用JankStats进行现场收集:使用JankStats 库从应用的特定部分收集帧渲染时间,并记录和分析数据。
- 在使用 Macrobenchmark(底层使用 Perfetto)的测试中
- Perfetto FrameTimeline:在 Android 12(API 级别 31)上,您可以从 Perfetto 跟踪中收集帧时间线指标,以了解哪些工作导致帧丢失。这可能是诊断帧丢失原因的第一步。
- 用于卡顿检测的 Android Studio Profiler
检查主活动加载时间
您的应用主活动可能包含大量从多个来源加载的信息。检查主页Activity
布局,特别是查看主页活动的Choreographer.onDraw
方法。
- 使用
reportFullyDrawn
向系统报告您的应用现已完全绘制以进行优化。 - 使用 Macrobenchmark 库中的
StartupTimingMetric
测量活动和应用启动。 - 查看帧丢失。
- 识别渲染或测量时间过长的布局。
- 识别加载时间过长的资源。
- 识别启动期间膨胀的不必要布局。
考虑这些可能的解决方案以优化主活动加载时间
- 使您的初始布局尽可能基本。有关更多信息,请参阅优化布局层次结构。
- 添加自定义跟踪点以提供有关帧丢失和复杂布局的更多信息。
- 最大限度地减少启动期间加载的位图资源的数量和大小。
在布局并非立即
VISIBLE
的情况下使用ViewStub
。ViewStub
是一个不可见的、零大小的 View,可用于在运行时延迟膨胀布局资源。有关更多信息,请参阅ViewStub
。如果您使用的是Jetpack Compose,您可以使用状态来延迟加载某些组件,从而获得与
ViewStub
类似的行为var shouldLoad by remember {mutableStateOf(false)} if (shouldLoad) { MyComposable() }
通过修改
shouldLoad
代码,在条件块内加载可组合项。LaunchedEffect(Unit) { shouldLoad = true }
这会触发一次重组,其中包括第一个代码片段中条件块内的代码。