性能优化之 启动时间

和你一起终身学习,这里是程序员 Android

本篇文章主要介绍 Android 开发中的部分知识点,通过阅读本篇文章,您将收获以下内容:

1.应用启动时间概述

1. 应用启动时间概述

用户期望应用程序能够快速响应并快速加载。启动时间慢的应用程序不能满足此期望,并且可能会使用户感到失望。这种糟糕的体验可能会导致用户在Play商店中对您的应用进行差评,甚至完全放弃您的应用。

本文档提供了可帮助您优化应用程序启动时间的信息。首先说明启动过程的内部。接下来,讨论如何分析启动性能。最后,它描述了一些常见的启动时问题,并提供了有关如何解决这些问题的提示。

2. 了解应用启动内部机制

应用程序启动可以分以下三种状态,每种状态都会影响您的应用程序对用户可见的时间:冷启动,热启动或热启动。

冷启动:您的应用将从头开始。在其他状态下,系统需要将正在运行的应用程序从后台移到前台。我们建议您始终基于冷启动的假设进行优化。这样做也可以改善热启动和热启动的性能。

为了优化您的应用程序以使其快速启动,了解每种状态下系统和应用程序级发生的情况以及它们之间的交互方式非常有用。

3. 冷启动

冷启动是指应用程序从头开始,比如设备重启,或者app 进程被杀掉,首次启动应用程序时为冷启动。这种类型的启动在优化启动时间方面提出了最大的挑战,因为与其他启动状态相比,系统和应用程序要做的工作更多。

在冷启动的开始,系统具有三个任务。这些任务是:

  1. 加载并启动应用程序。

  2. 启动后立即为应用显示空白的启动窗口。

  3. 创建应用程序 流程。

一旦系统创建了应用程序流程,该应用程序流程将负责以下阶段:

  • 1.创建应用程序对象。

  • 2.启动主线程。

  • 3.创建主Activity。

  • 4.填充视图。

  • 5.显示布局。

  • 6.执行初始化绘制。

一旦应用程序进程完成了第一次绘制,系统进程就会交换出当前显示的背景窗口,将其替换为主要Activity。此时,用户可以开始使用该应用程序。

图1显示了系统和应用程序之间如何相互切换工作。

640?wx_fmt=other

图1.可视化表示冷应用程序启动的重要部分

在创建应用程序和创建Activity 期间可能会出现性能问题。

4. app 创建

启动应用程序时,空白的启动窗口会保留在屏幕上,直到系统首次完成绘制应用程序为止。此时,系统进程会换出您应用程序的启动窗口,从而允许用户开始与应用程序进行交互。

如果您已在应用程序的Application.onCreate() 方法中重载,则系统会onCreate()在您的应用程序对象上调用此方法。之后,该应用程序会运行在主线程(也称为UI线程),并通过创建MainActivity来执行其他任务。

从这一点开始,系统和应用程序级别的流程将根据 应用程序生命周期阶段进行。

5.创建Activity

应用程序流程创建Activity后,该Activity将执行以下操作:

  1. 初始化值。

  2. 调用构造函数。

  3. 调用 Activity.onCreate() 适合于活动当前生命周期状态的回调方法,例如 。

通常,该 onCreate() 方法对加载时间的影响最大,因为该方法以最高的开销执行工作:加载和扩展视图,以及初始化Activity运行所需的对象。

6. 热启动

与冷启动相比,热启动应用程序更简单,开销也更低。在热启动中,系统要做的就是将您的Activity置于前台。如果应用程序的所有Activity 仍驻留在内存中,则该应用程序可以避免重复对象初始化,布局填充和渲染。

但是,如果由于内存调整事件(例如)清除了某些内存 onTrimMemory() ,则需要热启动事件来重新创建那些对象。

热启动显示的屏幕行为与冷启动场景相同:系统进程显示空白屏幕,直到应用程序完成活动呈现为止。

7. 暖启动

暖启动包括在冷启动期间发生的部分操作。同时,它显示的开销比热启动还多。有许多可能的状态可以认为是暖启动。例如:

  • 用户退出您的应用程序,然后重新启动它。该过程可能继续运行,但是应用必须通过调用从头开始重新创建Activity onCreate() 。

  • 您的应用程序从系统内存中退出,但是用户又重新启动它,这时进程和Activity需要重新启动,但是task 可以从已保存实例状态传递到Activity 中的 onCreate() 。

8.检测和诊断app 启动问题

Android提供了多种方法来告知您您的应用程序有问题,并帮助您进行诊断。Android vitals可以提醒您问题正在发生,并且诊断工具可以帮助您诊断问题。

Android vitals

当App的启动时间过长时 ,Android vitals 可以通过Play控制台提醒您,从而帮助提高应用的性能。当以下情况时,Android vitals 认为您的应用程序的启动时间过长:

  • 冷启动需要5秒钟或更长时间。

  • 暖启动需要2秒钟或更长时间。

  • 热启动需要1.5秒或更长时间。

Android vitals不会报告热启动 的数据。有关Google Play如何收集Android生命数据的信息,请参阅 Play控制台文档。

9.分析app 启动时间

为了正确诊断启动时间性能,您可以跟踪显示应用程序启动需要多长时间的指标。

initial display 初始化时间

在Android 4.4(API级别19)及更高版本中,logcat包含包含名为的值的输出行Displayed,如需查看,可以搜索关键字Displayed。Displayed

  • 启动 app 进程。

  • 初始化对象。

  • 创建并初始化Activity。

  • Inflate填充布局。

  • 第一次绘制您的App。

抓取的log日志来类似于以下示例:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms

如果要从命令行或在终端中跟踪logcat输出,则查找显示时间很简单。要在Android Studio中查找显示时间,必须在logcat视图中禁用过滤器。必须禁用过滤器,因为系统服务器(而不是应用程序本身)会提供此日志。

进行适当的设置后,您可以轻松搜索正确的词Displayed以查看时间。图2显示了如何禁用过滤器,并在底部的第二行输出中显示了Displayed时间的logcat输出示例。

640?wx_fmt=other

图2.禁用过滤器,并Displayed在logcat中找到值。

Displayed 在 logcat输出中的指标并不一定捕获加载和显示所有资源之前的时间:它会忽略布局文件中未引用的资源或应用程序作为对象初始化的一部分创建的资源。它排除了这些资源,因为加载它们是一个内联过程,并且不会阻止应用程序的初始显示。

有时,Displayedlogcat输出中的行包含一个用于总时间的附加字段。例如:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms (total +1m22s643ms)

这种情况下,第一次测量启动时间仅仅是Activity第一次绘制时间。Total时间是用于测量从app 进程启动到显示,其中可能包括其他非MainActivity的启动,虽然他们没有启动并且没有在屏幕上显示,但是它在mainActivity 中被调用,故在启动的时候也会被初始化上。这种**Total模式仅适用于 Single Activity模式。

当然了,您还可以通过使用ADB Shell 命令运行您的应用来测量首次显示的时间 。这是一个例子:

adb [-d|-e|-s <serialNumber>] shell am start -S -W
com.example.app/.MainActivity
-c android.intent.category.LAUNCHER
-a android.intent.action.MAIN

该Displayed指标与以前一样出现在logcat输出中。您的终端窗口也应显示以下内容:

Starting: Intent
Activity: com.example.app/.MainActivity
ThisTime: 2044
TotalTime: 2044
WaitTime: 2054
Complete

-c-a参数是可选的,让你指定 <category> 并<action> 为意图。

reportFullyDrawn() 测量全显示时间

您可以使用 reportFullyDrawn() 方法来测量从App启动到完整显示所有资源以及View 层次结构之间经过的时间。在应用执行延迟加载的情况下,这可能很有价值。在延迟加载中,应用程序不会阻止窗口的初始绘制,而是异步加载资源并更新View视图层级。

如果由于延迟加载而导致应用的初始显示不包括所有资源,那么您可以将所有资源和视图的完成加载和显示视为一个单独的指标:例如,您的UI可能已完全加载,并绘制了一些文本,但尚未显示该应用必须从网络获取的图像。

为了解决此问题,您可以手动调用 reportFullyDrawn() 让系统知道您的Activity 已完成其延迟加载。使用此方法时,logcat显示的值是从创建应用程序对象到 reportFullyDrawn() 调用此刻为止所经过的时间 。这是logcat输出的示例

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms

logcat输出有时包含一个total时间,如 “初始显示时间”中所述。

如果您发现显示时间慢于所需时间,则可以继续尝试确定启动过程中的瓶颈。

10. CPU Profiler

查找瓶颈的一种好方法是使用Android Studio CPU Profiler。有关信息,请参阅 使用CPU Profiler检查CPU活动。

您还可以通过在应用程序和活动onCreate()方法的内部进行内联跟踪来深入了解潜在的瓶颈。要了解内联跟踪,请参阅 Trace 功能文档和systrace概述。

11. 常见问题举例

本节讨论了一些通常会影响应用程序启动性能的问题。这些问题主要涉及初始化应用程序和活动对象以及屏幕加载。

过多的应用初始化

当您的代码覆盖Application 对象,并在初始化该对象时会执行繁重的工作或复杂的逻辑运算,则App启动性能会受到影响。如果您的应用程序子类执行不需要完成的初始化,则您的应用程序可能是在浪费启动时间。某些初始化可能是完全不必要的:例如,当App 通过Intent 启动时,初始化Main Activity 的状态信息是件错误的选择。通过Intent,该应用程序仅使用先前初始化的状态数据的子集。

应用程序初始化期间的其他有影响有 频繁的垃圾回收事件,或初始化时同时发生的磁盘I / O,这些都阻碍了初始化过程。Dalvik运行时尤其要考虑垃圾回收机制。Art运行时同时执行垃圾回收,从而最大程度地减少了该操作的影响。

12.分析诊断 app 启动时间

您可以使用方法跟踪或内联跟踪来尝试诊断问题。

方法跟踪

运行CPU Profiler会显示该 callApplicationOnCreate() 方法最终会调用您的com.example.customApplication.onCreate 方法。如果该工具表明这些方法需要很长时间才能完成执行,则应进一步探索以查看正在执行的工作。

内联跟踪

使用内联跟踪来调查可能的罪魁祸首,包括:

  • 您应用的初始 onCreate() 功能。

  • 您的应用初始化的所有全局单例对象。

  • 在瓶颈期间可能发生的任何磁盘I / O,反序列化或紧密循环。

解决问题的方法

无论问题出在不必要的初始化还是磁盘I / O上,该解决方案都需要延迟初始化对象:仅初始化那些立即需要的对象。例如,改为创建单例模式,而不是创建全局静态对象,在该模式下,应用仅在首次访问对象时才初始化对象。另外,考虑使用像Dagger 这样的依赖项注入框架来创建对象,并且依赖项是在首次注入对象时创建的。

过多的Activity 初始化

Activity 创建通常需要大量的开销工作。通常,有机会优化这项工作以提高性能。这些常见问题包括:

  • 填充过大或复杂的布局。

  • 阻止磁盘或网络I / O上的屏幕绘制。

  • 加载和解码位图。

  • 栅格化 VectorDrawable 对象。

  • 初始化活动的其他子系统。

诊断问题

同样在这种情况下,方法跟踪和内联跟踪都可以证明是有用的。

方法跟踪

使用CPU Profiler时,请注意应用程序的 Application 子类构造函数和 com.example.customApplication.onCreate()方法。

如果该工具表明这些方法需要很长时间才能完成执行,则应进一步探索以查看正在执行的工作。

内联跟踪

使用内联跟踪来调查可能的罪魁祸首,包括:

  • 您应用的初始 onCreate() 功能。

  • 它初始化的所有全局单例对象。

  • 在瓶颈期间可能发生的任何磁盘I / O,反序列化或紧密循环。

解决问题的方法

有许多潜在的瓶颈,但是两个常见的问题和补救措施如下:

  • 视图层次结构越大,应用程序花费的时间就越多。您可以采取两个步骤来解决此问题:

    • 通过减少冗余或嵌套布局来展平视图层次结构。

    • 不膨胀在启动过程中不需要显示的UI部分。而是将 ViewStub 对象用作占位符,用于应用程序可以在更合适的时间膨胀的子层次结构。

  • 在主线程上进行所有资源初始化也会降低启动速度。您可以按以下方式解决此问题:

    • 移动所有资源初始化,以便应用程序可以在其他线程上延迟执行它。

    • 允许该应用加载并显示您的视图,然后在以后更新依赖于位图和其他资源的视觉属性。

13. 添加屏幕启动Theme

您可能希望以应用程序的加载体验为主题,以使应用程序的启动屏幕在主题上与应用程序的其余部分保持一致,而不是与系统主题保持一致。这样做可能会掩盖启动缓慢的活动。

实施主题启动屏幕的常用方法是使用 windowDisablePreview 主题属性关闭启动应用程序时系统进程绘制的初始空白屏幕。但是,与不隐藏预览窗口的应用程序相比,这种方法可能导致启动时间更长。此外,它会迫使用户在activity 启动时等待任何反馈,而无需等待,这使他们想知道应用程序是否正常运行。

诊断问题

您通常可以通过观察用户启动您的应用程序时响应缓慢来诊断此问题。在这种情况下,屏幕可能似乎冻结了,或者已停止响应输入。

解决方案

我们建议您遵循通用的 Material Design模式,而不是禁用预览窗口 。您可以使用活动的 windowBackground主题属性为开始的活动提供一个简单的自定义可绘制对象。

例如,您可以创建一个新的可绘制文件,并从布局XML和应用清单文件中引用它,如下所示:

布局XML文件

<layer-list xmlns:android="http://schemas.android.com/apk/res/android" android:opacity="opaque">
 <!-- The background color, preferably the same as your normal theme -->
 <item android:drawable="@android:color/white"/>
 <!-- Your product logo - 144dp color version of your app icon -->
 <item>
   <bitmap
     android:src="@drawable/product_logo_144dp"
     android:gravity="center"/>
 </item>
</layer-list>

Manifest.xml 文件

<activity ...
android:theme="@style/AppTheme.Launcher" />

调用方法

public class MyMainActivity extends AppCompatActivity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    // Make sure this is before calling super.onCreate
    setTheme(R.style.Theme_MyApp);
    super.onCreate(savedInstanceState);
    // ...
  }
}

友情推荐

至此,本篇已结束,如有不对的地方,欢迎您的建议与指正。同时期待您的关注,感谢您的阅读,谢谢!

640?wx_fmt=png

posted @ 2019-10-22 10:02  程序员Android的博客  阅读(201)  评论(0编辑  收藏  举报