Android 最佳实践-性能-启动性能

原文链接 https://developer.android.com/topic/performance/vitals/launch-time

APP 启动时间

用户希望app及时反应加载快速。一款启动时间过长的APP不符合用户的预期。这样的体验会让APP在play store的评分很低,甚至用户会完全放弃这款APP。

这篇文章给你提供优化启动时间的信息。首先会介绍启动进程的内部机制。接下来讨论如何描述启动性能。最后,提出一些常见的启动问题并给出解决方案。

理解APP启动的内部机制

APP会在三种情况之一下启动,每一种都会影响从APP启动到用户可见的这段时间。分别是冷启动,暖启动以及热启动。冷启动状态下APP的启动从零开始,其他状态下,系统将运行中的APP由后台转到前台。推荐在假定为冷启动的情况下进行优化,这样暖启动和热启动同样收益。

为了优化启动时间,了解在这些状态下,系统层和APP层发生了什么,已经它们之间是如何交互的,是有所帮助的。

冷启动

    冷启动是指App完全从零开始启动,系统直到这次启动才创建了APP进程,冷启动会在一些情况下发生,比如,系统启动后第一次打开这个APP,或者在启动之前系统杀死了这个APP进程,这种启动给最小化启动时间带来了巨大的挑战,相比其他状态系统和APP层面需要做更多的工作。

在冷启动的开始,系统有三个任务:

    1.加载和启动APP。

    2.启动时马上显示一个空白的窗口。

    3.创建APP进程。

一旦APP进程创建完成,会进入下一个阶段:

    1.创建APP对象。

    2.启动主线程。

    3.创建主activity。

    4.展开视图。

    5.布局屏幕。

    6.开始初始绘制。

一旦APP进程完成了首次绘制,系统将当前显示的窗口置换为主activity,那时用户就可以使用APP了。

图1,显示了系统和APP进程是如何彼此切换工作的。


图1.展示了冷启动的重要环节。

性能问题可能在创建Application和Activity时产生。

创建Application

    启动App时,屏幕上会先显示一个空白的启动窗口指导App完成了首次绘制。之后系统会置换出该窗口,并让用户进行交互。

如果你重载了Application.onCreate方法,系统会在启动时调用该方法。之后App会产生线程,也就是UI线程,会在这个线程上

进行主Activity创建,之后系统和App层面的活动会依照App生命周期来进行。

Activity的创建

    在App进程创建Activity之后,Activity会进行如下的操作:

        1.初始化值

        2.调用构造器

        3.调用回调方法:比如Activity.onCreate(),分配对应的生命周期状态。

通常来说,onCreate方法会给加载时间带来最大的影响,因为执行了最繁重的任务:读取和展开视图,创建Activity运行需要的对象。

热启动

    热启动比冷启动简单很多并且加载任务更少。热启动中,系统所做的事情就是把Activity从后台调到前台。如果应用所有的Activity都驻留在内存,那么这个App就避免了重复对象初始化,视图展开和渲染。

    然而,如果一些内容因为内存整理事件onTrimMemory而被释放,那么久需要在热启动的时候重写创建了。热启动会跟冷启动在视觉上保持一致:系统进程会展开一个空白页,直到App完成渲染。

暖启动

    暖启动包含了一些在冷启动时进行的操作,与此同时,耗费比热启动更少的时间。这里有一些潜在的状态 可以认为是暖启动。比如 

    1.用户返回并退出App,然后重新打开。进程可能还在运行,但是App必须通过onCreate重新创建和调用。

    2.系统将App移出内存,然后用户重新启动了App,系统将重新创建进程和Activity,但是保留状态信息将传递给oncreate方法。

检测和诊断问题

    Android有一些好的方法能让你了解到你的App有问题,并且诊断问题。

Android vital 能提醒你问题正在发生,在play console 上 通过提醒你的App启动时间过长帮助你改进性能。Android vital 会在以下的情形下,认为App启动时间过长:

    冷启动花费了5秒或更长时间

    暖启动花费2秒或更长时间

    热启动花费1秒或更长时间

诊断启动时间

    为可正确地诊断时间,你可以查看App启动时间。

初始显示的时间


android 4.4或以上,logcat 包含了一个名为displayed 的值的输出行。这是值代表了启动进程和完成相应的activity显示花费的时间
这个时间花费包含了以下的一系列事件


1.启动进程
2.初始化对象
3.创建和初始化activity
4.展开布局
5.首次渲染app


该输出行类似如下:

ActivityManager: Displayed com.android.myexample/.StartupTiming: +3s534ms
如果你使用命令行或者终端来追踪logcat,你会发现该值是直白的。为了在android stuio 查看 花费时间,你必须在locat视图禁用过滤器。禁用过滤器是必须的,因为这个日志由系统提供而不是app本身。一旦设置正确,你可以很容易的查找到正确的词语表示花费时间。图示2,显示如何禁用过滤器,倒数第二行的logcat输出,是一个displayed 时间的例子。




displayed 这个值表示所有资源加载和显示的时间。忽略了那些没有被布局文件引用的资源文件和那些被当做app初始化对像的一部分的资源。不包含这些资源的原因是加载他们是由内联进程完成的,不会阻塞app的初始显示。

有时候,logcat 的 displayed 输出行会带有总共时间这个额外的数值。

这种情况,第一次测定的仅仅是第一次绘制activity的时间,总共时间包含了从app进程启动开始,以及其他第一次启动但是没有显示的activity所花费的时间。总共时间只有在启动单个acitvity和总共启动时间不同时才会显示。

你也可以通过 adb shell activity manager 命令行来测定 app 初始显示所花费的时间。这里是个例子

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

logcat 上的 displayed 值跟之前显示的一样。终端窗口则应该显示如下内容

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


完全显示需要的时间

        你也可以使用 reportFullDraw() 这个方法来 测定 从 app启动到完全显示所有的资源和视图的时间。这在app执行的是懒加载的时候是很有用的,app不会阻塞窗口的绘制,而是通过异步执行来加载资源以及更新视图。这样,因为懒加载,你可能会认为完全加载资源和显示视图是一个独立度量。举个例子,你的UI可以已经完全展示了文字,但是还没有展示那些必须从网络下载的图片。

为了解决这个问题,你可以手动调用reportfullDraw()这个方法告诉系统activity已经完成了懒加载。当你使用这个方法的时候,logcat显示的时间是从application 对象的创建开始 直到 这个方法的执行。

以下是一个logcat的例子

system_process I/ActivityManager: Fully drawn {package}/.MainActivity: +1s54ms
logcat 输出有时候包括了total 时间,如上面的 初始显示时间 讨论的一样
如果显示时间比你希望的要慢,那么你可以尝试去定位启动进程的瓶颈。

定位瓶颈

    有两个好方法来查找瓶颈:android stuio method tracer 和 内联追踪。可以通过 method tracer的文档了解该工具,如果你没有权限使用这个工具或者没有很好的时机来收集logcat信息,你可以放置内联追踪方法在app和activity的oncreate方法,也能达到相似的目的可以查看 trace 方法以及systrace 工具的文档来了解。

认识常见问题

        这一节讨论一些常见的影响app启动性能的问题。这些问题主要是关于 创建 app和activity对象以及加载屏幕的


繁重的app初始化

    启动性能会受到影响,当你重写application的方法,以及在创建的过程中执行繁重的任务和复杂的逻辑。你的app可能会在启动时浪费时间,因为app在初始化的时候做了一些不必要的工作。某些初始化是不必要的。比如,当acitivy响应了intent的请求启动时,为主activity初始化状态信息。app 只使用了intent里一些早前初始化的状态信息。

    app初始的其他挑战包括了垃圾回收事件,它可能有显著影响或者调用次数过多,磁盘 I/O 操作,这些可能阻塞初始化的执行。垃圾回收在devlik运行时是尤其需要考虑。art同步的使用垃圾回收,最小化对性能的影响。


诊断问题

你可以使用方法追踪和内联追踪来确定问题

方法追踪

运行这个工具,通过callapplicationOnCreate()方法最终调用application的oncreate方法。如果工具显示这个过程花费了过长的时间
你应该进一步查看这中间发生了什么。

内联追踪

使用内联追踪查看可能的问题原因包括:
app初始化调用oncreate方法

任何的全局单例初始化,发生在瓶颈时间的,磁盘I/O 反序列化,紧循环(tighten loop)


解决问题

有很多潜在的瓶颈,有两个常见的问题和解决方案:

    视图结构越庞大,就需要更多的时间来展开。你可以分两步解决这个问题。

        通过减少冗余和嵌套对视图进行扁平化

        不要展开那些启动的时候不可见的UI,使用viewstub做为占位符,在更合适的时候展开它。

在主线程加载所有的资源也会影响启动性能,你可以按如下的方法解决这个问题。

        在其他线程进行资源加载

        先展示视图,稍后再更新那些依赖图片或其他资源的部分。

为启动界面赋予主题

    你可能希望为App的加载加上主题效果。这样启动界面的风格可以和其他部分的风格保持一致,这可以隐藏App启动缓慢的问题。

    一个常用的方法是通过指定windowdisbalepreview属性来禁用系统启动app时显示的空白窗口,这会导致App启动延长,与此同时,用户需要等待App响应,这会让人怀疑这个App是否正常工作。

    确定问题

    通常可以通过观察用户的启动App时的缓慢反应来判断问题的所在。这种情况下,屏幕看起来像冻结了,或者停止响应用户的输入事件。

问题的解决方案

    我们推荐使用材料设计模式而不是禁用预览窗口,你可以使用windowsbackground这个属性为启动提供自定义视图。

    例如:

    

Layout XML file:

 
<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 file:

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

回到普通主题的最简单方法是在调用super.oncreate()和setContentView()之前 调用setTheme(R.style.AppTheme) 

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);
    // ...
  }
}

   

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值