Android性能优化(四)之启动速度优化

一、前言

一款好的产品应该集界面美观、操作流畅、功能稳定于一身,但是随着App功能的增多,App的性能问题会逐渐暴露出来,比如卡顿、闪退等现象。这些性能问题极大的影响了用户体验。因此,在APP的开发维护中,预防和解决性能问题显得尤为重要。日常工作和学习中,我也比较重视这一块的实战和总结,但性能优化是个大话题,我将用一个专题来记录,作为今后开发的Check List。

APP的启动速度是用户体验的第一扇门,所以第一篇文章就从应用的启动优化开始,优化APP的启动速度。

二、启动加速概述

Google官方文档《Launch-Time Performance》将应用的启动分为冷启动、热启动、温启动三种。

冷启动

应用程序自设备启动以来第一次启动或系统杀死应用程序等情况(后台没有该应用的进程)下启动应用,这种方式叫冷启动;

热启动

当启动应用时,后台已有该应用的进程(例:按back键、home键,应用虽然会退出,但是该应用的进程是依然会保留在后台),启动的时候会从已有的进程中来启动应用,这个方式叫热启动。

温启动

用户退出应用,但随后重新启动。该过程应用程序必须通过调用onCreate()从头开始重新创建活动。进程和Activity需要重新启动,但任务可以从保存的实例状态包传递到onCreate()中。

通过上面的描述可以得知冷启动这种类型的启动在优化启动速度方面挑战最大,因为系统和应用程序比其他启动状态需要做更多的工作。

应用在冷启动之前,需要执行三个任务:

  1. 加载启动App;
  2. App启动之后立即展示出一个空白的Window;
  3. 创建App的进程;

而这三个任务执行完毕之后会马上执行以下任务:

  1. 创建App对象;
  2. 启动Main Thread;
  3. 创建启动的Activity对象;
  4. 加载View;
  5. 布置屏幕;
  6. 进行第一次绘制;

应用冷启动流程图

而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。

上述流程里面大多数步骤都是由系统控制的,作为普通应用,可以优化的也就是以下3个方面:

  1. 利用闪屏图,视觉加速;
  2. 优化Application的onCreate流程
  3. 优化Activity的onCreate流程

接下来将针对这3点优化方向具体展开

三、启动加速之闪屏图

使用Activity的windowBackground主题属性可以为启动的Activity提供一个简单的drawable,实现闪屏图效果

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" />

onCreate:

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

目前大多数应用都会使用这种方式来替换系统默认的启动窗口,不仅可以作为品牌宣传页,还可以视觉上产生一种快速启动APP的感觉。也有些APP通过关闭启动窗口属性android:windowDisablePreview的方式来直接移除系统默认的启动窗口,但是这样的弊端是用户从点击桌面图标到真的看到实际页面的这段时间当中,画面没有任何变化,实际上是将启动延迟的锅丢给了手机开发商。

不过上述方式其实并没有真正的实现启动速度优化,只是通过视觉体验来优化了交互效果。

四、启动加速之Application

通常我们会在Application里面做一些第三方组件、数据库等初始化工作,但如果不做选择,所有的初始化工作都放到Application的主线程中处理,必然会导致主线程阻塞,拖慢APP的启动速度。所以,在Application的onCreate里面,我们需要根据组件的功能重要程度以及优先级,按照如下两种方案处理:

  • 异步加载,不阻塞主线程;

数据库及IO操作都移到工作线程,尝试设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样工作线程最多能获取到10%的时间片,优先保证主线程执行。

  • 延迟加载,延迟到组件真正被调用到的时候再做加载

是否可以延迟加载取决组件的调用关系以及具体业务的需要。保证一个准则:可以异步的都异步,不可以异步的尽量延迟。让应用先启动,再操作。

五、启动加速之Activity

在Activity的创建加载过程中,会执行很多操作,例如设置页面的主题,初始化页面的布局,加载图片,获取网络数据,读写SharedPreference等。
上述操作的任何一个环节出现性能问题都可能导致画面不能及时显示,影响APP的启动速度。

对Activity启动做性能优化,我们需要考虑以下两点:

  1. 优化布局耗时:一个布局层级越深,里面包含需要加载的元素越多,就会耗费更多的初始化时间。
  2. 异步延迟加载:一开始只初始化最需要的布局;异步加载图片,非立即需要的组件可以做延迟加载。

五、常用工具:

1.display time

从Android KitKat(4.4)版本开始,Logcat中会输出从程序启动到某个Activity显示到画面上所花费的时间。这个方法比较适合测量程序的启动时间。

2.adb命令

通过ADB命令也可以统计应用的启动时间:

adb shell am start -W [packageName]/[packageName.MainActivity]

指标含义:
ThisTime:一般和TotalTime时间一样,除非在应用启动时开了一个透明的Activity预先处理一些事再显示出主Activity,这样将比TotalTime小;
TotalTime:应用的启动时间,包括创建进程+Application初始化+Activity初始化到界面显示;
WaitTime: ActivityManagerService启动App的Activity时的总时间,一般比ToatalTime大。

3.调用Activity的reportFullyDrawn()方法

上面两种方式统计到的启动时间是自动报告的,对于使用异步加载(程序界面已经显示,可是内容却还在加载)的情况就无法很好的统计时间了。为了统计异步加载所耗费的时间,我们可以在异步加载完毕之后调用Activity的reportFullyDrawn()方法获取整个加载的耗时。

在4.4上调用reportFullyDrawn()方法会崩溃(但是log还是能正常打印),提示需要UPDATE_DEVICE_STATS权限 ,但是这个权限只有系统app才能授权。解决的办法是这样调用:

try{
     reportFullyDrawn();
   }catch(SecurityException e){
}

reportFullyDrawn()方法显示的log类似display time。

4.Method Tracing

计算出APP启动的总时间后,我们需要分析具体的耗时细节。为了获取具体的耗时分布情况,我们可以使用Android studio自带的Method Tracing工具来进行详细的测量。

六、总结

  • 利用闪屏图;
  • 异步初始化组件;
  • 梳理业务逻辑,延迟初始化组件、操作;
  • 正确使用线程;
  • 去掉无用代码、重复逻辑等。