0%

App启动优化

最近在做App启动优化,参考了一些文章,就最近做的一些事做一个总结输出。

背景

特价版主打下沉市场,中低端机型占比78%,一直被吐槽首页和启动性能很差,魔兔数据一直在4.5s+。从特价版重启1年半左右,业务发展迅速,团队也越来越壮大,启动任务也越来越臃肿,在这过程中,特价版也没有做启动优化专项整治和建立完善的机制。

所以2020 S1,我们建立基础体验专项团队,并和手淘团队一起解决端上的体验问题。启动耗时从4.5s降低到1.5s。排名集团第一,超越竞对。

image.png

线下录屏vivo y67口径,一刷的耗时1.7s,二刷耗时3.5s。

前言分析

我们将启动中优化点分为三部分。

  1. 启动框架。合理的使用启动框架,解决多任务依赖问题,高效的执行任务。
  2. 提高任务执行效率。高效执行多依赖任务;减少集中处理任务,将任务打散。
  3. 任务是否需要主线程。去除不合理的主线程任务耗时。

AB能力

AB能力一个是可以通过开关配置启动优化,降低风险;另外一个是通过AB统计优化数据的对比。
通过AB可以实现对技术数据的对比(启动速度,crash);也可以对业务数据进行对比,比如跳失率、首页曝光和点击率等。

技术优化

启动框架

特价版现有的启动框架是基于手猫的一套启动框架,提供简单线性依赖和多线程调度,在越来越复杂的业务中,逐渐没办法满足当前的一些场景。

开发框架调研:

手淘next-laucher:

  • 优点:
    手淘启动框架基于DGA图去管理所有任务,配合PanguApplication实现所有任务的生命周期,功能上能够满足我们的需求。
    有完善的调试视图工具和线上数据上报。
  • 缺点:
    集成大小增加400k+。
    添加任务和依赖配置对于一个快速迭代的业务来非常的麻烦。

特价版追求极致的apk大小和快速开发,所以我们最终评估自研一套启动框架。

新启动框架(DGAppStartup)

1 新框架的设计也是基础DGA图的模式管理任务,框架分为Group和Task的概念,我们将Group定义为一个完成的启动任务流程,Task表示每个任务。比如我们需要在app启动的时候执行一串任务,在首页可流畅交互后执行一串任务,也就分为两个group。启动详细设计

image.png

2 Task编写的Api参考官方启动框架AppStartup,提供任务优先级,执行线程配置,是否阻塞主线程。

image.png

3 支持任务trace
通过配置开启Task Trace,排查锁等待和耗时问题。

image.png

4 线上关联APM耗时统计

image.png

5 将任务打散拆分多个阶段

image-20201018183759451.png

6 支持启动任务阶段任务ANR的检测。

结果
正式环境放量5%对比,A2桶。提升100ms左右。最重要的是我们开发同学在编写任务更加的清晰可控。
image-20201018183759451.png

任务优化

从用户的体感,启动流程包括几个阶段 ,首先是点击图标,创建App进程,Application的创建,首页Activity的创建,首页数据渲染。其中我们保证首页的渲染速度需要保证基础任务和网络、图片初始化任务。

Trace高阶使用可以看下具体使用

1 耗时任务排查

通过method trace排查启动过程中相关的耗时任务,这里仅展出部分。

image.png

网络数据请求返回后主线程解析

image.png

2 首页布局耗时问题

首页相对来说还是比较复杂的,首页的瀑布流被划分成了多个区域块,每个区域块的adapter都有其自己的布局。目前阶段,首页大部分还是原生代码完成的,而各区块布局,均是采用的layout xml文件的方式来描述,因此在首页渲染的过程中,会有大量的inflate操作。

image.png

为了优化首页布局耗时问题,在启动任务的时候,对首页布局做预加载, 优化后减少200ms。

image.png

3 图片上屏优化

针对图片加载成功的回调handler事件,提高优先级。
main handler内有一个消息队列,当主线程执行消息过多的时候,后续的消息可能执行等待时间久,其核心原理是handler提供将消息直接添加到消息队头,提高消息message优先级。

4 多个进程问题

  • com.taobao.litetao:主进程
  • com.taobao.litetao:sandboxed_privilege_process0:uc&windvane独立进程
  • com.taobao.litetao:channel: push进程

image.png

启动过程拉起UC进程,因为我们用户权益或者活动相关的pop是动态化语言,基于js引擎解析,js引擎依赖UC So下载,UC So管理又由windvane管理,windvane初始化会拉起UC进程 。
通过研究Api,将windvane初始化剥离,直接判断UC So是否下载完成。将windvane初始化延迟到首页启动10s。
image.png

5 任务延迟初始化问题

任务打散以后,有些任务就移到闲时执行,但是面对的问题是有时候三方跳转或者splash点击快速跳转会导致快速进入某个业务页面。比如上面讲的uc初始化移动到闲时,这个时候三方跳转进入web页面或者pha页面可能uc没有初始化完成,

我们解决办法是通过Nav路由拦截,在进入某个业务的时候,执行任务初始化。

6 Mtop调优

1 mtop的请求依赖前置条件,不是发起一个mtop请求,就会立即发起网络请求的。大部分的时候,首次的mtop请求可能都会等待无线保镖和网络库的初始化,无线保镖自身有着多个插件,在你需要使用到对应插件的时候,才会初始化,而每个插件基本都会涉及到load dex和so的操作,在低端机上耗时还是非常大的。为了减少首页的二刷率,会考虑在启动阶段,提前发起首页首屏的mtop请求,以达到进入首页后,立即展示服务端的最新结果。首屏预取不仅可以带来更有效的曝光,还能显著提升用户的体验。

image.png

image.png

通过Trace文件分析mtop初始化线程可以分析mtop依赖关系。通过并发执行前置依赖任务快速初始化mtop。并且提前初始化可以避免一些任务主线程执行。

20201018172237

7 线程配置优化

启动框架扩展每个阶段可配置线程池,基于这点,启动阶段任务量最大,采用2倍的cpu线程个数跑任务。启动必要的任务完成后,需要保证app的流程交互,其他阶段任务采用cpu个数-1个线程。

总结

虽然启动优化整体的结果较好,但是过程中也暴露出一些问题。比如业务场景和启动之间的平衡,启动优化改造后对业务的影响。虽然我们在优化过程中通过AB控制放量,但首装启动无法AB、开发关注的是稳定性相关的数据,所以在优化过程中出现过一些业务关键数据降低幅度大情况。后续我们配合用增和业务团队完善关键数据指标,建立启动AB开关关联业务数据指标,及时发现和解决问题。

APM启动口径

1 如何计算一个页面完成绘制:
通过遍历view,当页面的控件的渲染超过高的80%宽的60%,即认为页面可视。

2 APM用了 SystemClock.uptimeMillis() 而没有用System.currentTimeMillis()作为计量各个指标的基本时间统计方式。System.currentTimeMillis()在早前的APM的版本中有涉及使用到,导致大量的未知的大时间,负数问题,不可解释的数据约占整体版本1/100,原因是System.currentTimeMillis()是手机的系统时间,用户可以在设置页面中修改,不稳定。后面经调研使用SystemClock.uptimeMillis(),SystemClock.uptimeMillis()不仅仅不可被用户修改并且它也是AMS和ActivityThread官方使用的时间值,也是Handler.delayPost计算发生时间的标准值。因此最后确定使用SystemClock.uptimeMillis()作为APM全局时间统计方式。不可解释的数据下降到占整体版本1/10000。

Android 8.+后的设备系统提供了Process.getStartUptimeMillis(),Process.getStartUptimeMillis()的时间是在ActivityThread的第一消息BIND_APPLICATION的时候Process.setStartTimes()设置进去的,有兴趣的同学可以看这一部分的代码,了解写8.+之后ActivityThread的细节上的区别。这个时间点是在Application.attachBaseContext之前的,能包含容器初始化的时间点。