07 | 08 启动优化:从启动过程看启动速度优化——学习总结

背景

在超市排队结账,扫码支付启动十几秒都还没完成,只能换一个工具支付?

目的

优化启动速度。

分析

1、启动过程分析

07 | 08 启动优化:从启动过程看启动速度优化——学习总结_第1张图片

  • T1 预览窗口显示
    eg: 微信 Theme 属性创建的预览窗口。可以禁用或者透明,那么这段时间依然看到的是桌面。
  • T2 闪屏显示
    微信进程和闪屏页创建完毕,一些列 inflate view …准备工作后,看到 “小地球”。
  • T3 主页显示
    用户可以看到微信的主界面。
  • T4 界面可操作
    启动完成后,微信还有很多工作需要继续执行,eg:小程序框架和进程、聊天、朋友圈界面预加载…,完成后,用户真正可以进行聊天。

2、启动问题分析

4个阶段,大多3个问题。TOP3

  1. icon 点击很久无响应
    Theme 禁用或者透明 导致。
  2. 首页显示太慢 即t3 显示
    启动流程复杂,eg :闪屏广告、热修复框架、插件化框架、大前端框架、所有装备工作 init。
  3. 首页显示后无法操作
    T3 展示慢,那么异步咋样?后果:首页白屏,或者首页后用户无法操作。

方案

启动优化方法与卡顿优化基本相同。
因为太重要。4个阶段

定位问题工具,分析工具

  • 工具选择:
    需要找性能损耗小,而且适用性广的工具。
    Traceview 性能损耗太大
    Nanoscope 暂时只支持 Nexus 6P 和 x86 模拟器,无法针对中低端机
    Simpleperf 的火焰图并不适合做启动流程分析
    systrace 可以很方便地追踪关键系统调用的耗时情况,但是不支持应用程序代码的耗时分析

选择:systrace + 函数插桩
它还可以看到系统的一些关键事件,例如 GC、System Server、CPU 调度等。

通过插桩,我们可以看到应用主线程和其他线程的函数调用流程。它的实现原理非常简单,就是将下面的两个函数分别插入到每个方法的入口和出口。

class Trace {
  public static void i(String tag) {
    Trace.beginSection(name);
  }
  
  public static void o() {
      Trace.endSection();
  }
}

class Test {
  public void test() {
    Trace.i("Test.test()");
    // 原来的工作
    Trace.o()}
}

只有准确的数据评估才能指引优化的方向,这一步是非常非常重要的。

优化方式

前提:
在拿到整个启动流程的全景图之后,我们可以清楚地看到这段时间内系统、应用各个进程和线程的运行情况,现在我们要开始真正开始“干活”了。

具体的优化方式:
闪屏优化、业务梳理、业务优化、线程优化、GC 优化、系统调用优化。

  • 闪屏优化
    即T2优化,Theme 页后的显示。

    预览闪屏???待查,今日头条采用的“预览闪屏”
    我比较推荐的做法是,只在 Android 6.0 或者 Android 7.0 以上才启用“预览闪屏”方案,让手机性能好的用户可以有更好的体验。

    微信做的另外一个优化是合并闪屏和主页面的 Activity,减少一个 Activity 会给线上带来 100 毫秒左右的优化。
    缺点:管理时会非常复杂,特别是有很多例如 PWA、扫一扫这样的第三方启动流程的时候。

  • 业务梳理
    砍掉无用,懒加载选择
    启动过程中,哪些是必须的、哪些是可以砍掉的、哪些是可以懒加载的,哪些是可以根据业务场景来决定不同的启动模式(eg,扫一扫启动只需要加载几个模块即可)。低端机,砍掉功能取舍。降级功能。

  • 业务优化
    梳理后剩下的都是启动必须的模块。
    抓大放小,优化耗时长的步骤。

    一些架构、各种回调集中执行,初始化耗时长。
    各种反射、Hook,这些历史包袱需要逐渐有勇气的去偿还。

  • 线程优化
    优化各个线程像填空题和解锁提一样,希望利用上所有碎片时间。
    因此主线程和各个线程一直都是满载的。

    线程优化,主要在于:
    减少 CPU 调度带来的波动,让应用的启动时间更加稳定。

    具体做法:
    控制线程数量。防止太多线程竞争CPU。
    因此,统一线程池,根据机器适配。

proc/[pid]/sched:
  nr_voluntary_switches:     
  主动上下文切换次数,因为线程无法获取所需资源导致上下文切换,最普遍的是 IO。    
  nr_involuntary_switches:   
  被动上下文切换次数,线程被系统强制调度导致上下文切换,例如大量线程在抢占 CPU。

通过 systrace 可以看到锁等待的事件,我们需要排查这些等待是否可以优化,特别是防止主线程出现长时间的空转。
07 | 08 启动优化:从启动过程看启动速度优化——学习总结_第2张图片

  • 很多启动框架,会使用 Pipeline 机制,根据业务优先级规定业务初始化时机。
    微信内部使用的 mmkernel、阿里最近开源的 Alpha 启动框架,它们为各个任务建立依赖关系,最终构成一个有向无环图。
    对于可以并发的任务,会通过线程池最大程度提升启动速度。如果任务的依赖关系没有配置好,很容易出现下图这种情况。

07 | 08 启动优化:从启动过程看启动速度优化——学习总结_第3张图片

  • GC 优化
    在启动过程,要尽量减少 GC 的次数。
  • 系统调用优化
  • 影响

I/O 优化

启动过程读了什么文件、多少个字节、Buffer 是多大、使用了多长时间、在什么线程等一系列信息。

07 | 08 启动优化:从启动过程看启动速度优化——学习总结_第4张图片

SharedPreference

在初始化的时候还是要全部数据一起解析。如果它的数据量超过 1000 条,启动过程解析时间可能就超过 100 毫秒。
07 | 08 启动优化:从启动过程看启动速度优化——学习总结_第5张图片

类重排

Dex 文件用的到的类和安装包 APK 里面各种资源文件一般都比较小,但是读取非常频繁。我们可以利用系统这个机制将它们按照读取顺序重新排列,减少真实的磁盘 I/O 次数。

启动过程类加载顺序可以通过复写 ClassLoader 得到。

class GetClassLoader extends PathClassLoader {
    public Class findClass(String name) {
        // 将 name 记录到文件
        writeToFile(name,"coldstart_classes.txt");
        return super.findClass(name);
    }
}

然后通过 ReDex 的Interdex调整类在 Dex 中的排列顺序,最后可以利用 010 Editor 查看修改后的效果。

资源文件重排

Facebook :“资源热图”相对比较完善,也建设了一些配套的Dashboard 工具,希望后续可以开源出来。
支付宝:https://mp.weixin.qq.com/s/79tAFx6zi3JRG-ewoapIVQ

  • 统计
    来确定,加载顺序,得到顺序列表。
  • 度量
    重排后,是否生效了。
  • 自动化
    任何代码提交都有可能改变启动过程中类和资源的加载顺序。
    如果完全依靠人工手动处理,这个事情很难持续下去。
    通过定制 ROM 的一些埋点和配合的工具,我们可以将它们放到自动化流程当中。

方法:
调整安装包文件排列需要修改 7zip 源码实现支持传入文件列表顺序,同样最后可以利用 010 Editor 查看修改后的效果。

这两个优化可能会带来 100~200 毫秒的提高
我们还可以大大减少启动过程 I/O 的时间波动。

黑科技慎用

Tinker 框架在加载补丁后,应用启动速度会降低 5%~10%
应用加固对启动速度来说简直是灾难。

参考文章

极客时间:
https://time.geekbang.org/column/article/0?cid=142
https://time.geekbang.org/column/article/74044

你可能感兴趣的:(高质量开发)