一、启动优化项目背景
2018年京东数科成立中台部门,要求将公司各种技术沉淀复用,实现降本增效提质,京东数科mPaaS应运而生,解耦、组件化、能力提升改造等成为重要的工作之一,启动速度正是在中台大战略的背景下被提上日程。本文主要分享京东金融移动端Android启动速度优化的实践。
在App功能日益增加和用户体验不断改良的今天,App启动速度,已然成为影响用户体验的第一道门槛。所谓快,其实是在用户感官上的一种反应,如果能够使用以上的手段对App的启动速度优化,虽然实际上启动时的总操作量可能并没有真正减少,但经过合理的先后顺序安排,可以使得某些不必要的任务,延后再执行,起到在App启动时,更轻量、更灵敏的作用,这样能够比较快的响应用户从Launcher点击Icon的操作,提升用户体验,让用户感觉到『快』。
二、启动流程详细过程分析
2.1 启动类型分析
App启动过程主要分为四种:冷启动、热启动、温启动、自定义启动。本文使用一个比较形象的图示分析和示意。
- 冷启动:App从无到有的启动,包括创建App进程、加载资源、启动Main Thread、初始化Activity等等;比如安装App后第一次启动或者杀掉进程后重新启动。如下图所示。
-
热启动:App进程在后台运行,系统只是把它从后台带到前台,展示给用户;比如:应用程序的前后台切换。
-
温启动:介于冷启动和热启动之间的启动,比如打开App,按返回键再重新启动。其过程如下图所示
-
自定义启动:还有一种启动是,从用户体验角度,结合应用自身业务特点,定义启动终点。其过程如下图所示。
2.2 冷启动过程分析
冷启动包含了启动的所有过程,在本文中详细介绍冷启动的优化过程。在具体的优化之前,首先需要了解AndroidApp的启动原理, App从点击桌面图标到看到App的主界面整个过程中经过了哪些步骤,这些步骤中那些过程是可以优化的。下图是App启动过程的一个大概描述。
整个启动过程如下:
- 点击启动App
- 加载预览空白Window
- 创建进程
- 创建Application
- 启动主线程
- 创建MainActivity
- 加载布局
- 布置屏幕
- 首帧绘制
如图中第一步到第三步是这些都是系统的行为,一般情况下我们是无法直接干预的。首帧绘制完成后,就可以认为启动已经结束,第四步到第九步到首帧绘制完成。
其过程如下图所示:
整个App启动过程中可以优化的地方包括主进程启动流程和主界面启动流程,主进程启动就是Application的创建过程,主界面启动就是MainActivity的创建过程。
Application调用中attachBaseContext最早被调用,随后是onCreate方法。其过程如下:
1)当应用启动时,空白窗口将会一直存在直到系统完成了应用的首次绘制操作,此时,系统会替换掉启动窗口,这是经常会出现点击图标不响应的问题。
2)如果覆写了Application.onCreate()方法,系统将会调用你的Applcation 的onCreate()方法。之后应用程序将会创建出主线程(也叫UI 线程),并执行创建主Activity 的过程。
当应用进程创建了Activity 后,Activity会执行以下操作:(1)初始化值;(2)调用构造方法;(3)调用当前生命周期的回调函数,例如onCreate()、onResume ()、onWindowFocusChange (),通常情况下,onCreate()方法对加载时间影响最大,因为它要执行的操作更加繁重:加载和构造view,还有初始化activity运行所需的对象。
三、启动速度测量与监控工具
在确定启动速度优化的方向和目标后,就需要测量和监控工具,帮助快速定位和发现问题,并且进行辅助测试和验证,这里介绍常用的集中监控与测试工具。
3.1 logcat日志
在logcat中过滤字段”Displayed”,如图所示
3.2 ADB命令
// 其中的AppstartActivity全路径可以省略前面的packageName
adb shell am start -W [packageName]/[AppstartActivity全路径]
执行后会得到三个时间:ThisTime、TotalTime和WaitTime,详情如下:
ThisTime:表示最后一个Activity启动耗时。
TotalTime:表示所有Activity启动耗时。
WaitTime:表示AMS启动Activity的总耗时。
一般来说,只需查看得到的TotalTime,即应用的启动时间,其包括 创建进程 + Application初始化 + Activity初始化到界面显示 的过程。
3.3 日志打点
日志打点是最为简便和准确的一种方式,可以很容易的找出问题的优化点,但日志打点代码有侵入性,修改成本高。日志打点主要要关注以下几个地方:
应用程序的生命周期节点。
启动时需要初始化的重要方法,例如数据库初始化,读取本地的一些数据。
其他耗时的一些算法。
3.4 视频定帧
主要是通过录屏,然后按帧播放分析时间,方便测试和竞品分析,但分析过程较为繁琐,需要有足够的采集的样本,并且需要反复测试和分析。
3.5 SGM-Mobile(APM)
SGM-Mobile(APM)是京东数科自研的一套致力于移动端分布式服务监控、跟踪、预警的综合服务治理解决方案,是实现全链路监控的基础,通过线上APM性能监控系统,能实时对线上App进行性能监控,以App的版本为维度进行数据分析,从而能更好的对App的性能进行了解,及早发布App可能存在的问题,及时优化完善。在本次启动优化过程中主要是引入APM并应用APM进行优化及监控。APM系统架构图如下:
APM功能架构图如下:
四、启动速度优化实践
4.1 主题切换
前面总结到,点击桌面上的应用图标,首先展示的是一个空白的默认启动背景,因系统局限,容易出现白屏和点击无响应的问题。为了避免白屏或者点击无响应的问题而影响体验,最有效的方法还是需要修改默认的空白背景,替换为app的默认启动页画面。在此优化过程中把启动预览窗口背景和闪屏页都修改为京东金融启动页图片,如图所示。
4.2 Application做减法
金融App经过几年的不断迭代优化,在启动时有很多冗余代码或者设计不合理的代码不断的写入进来,本次优化通过对各种任务进行梳理、重构优化等方式,实现对启动任务的有效管理。
在本次启动速度优化提升改造第一个要做的事情就是Application中的代码优化并做减法,删除废弃代码、冗余逻辑、无用业务、以及调整不必要在启动时初始化的代码。
4.3 线程优化
在完成启动业务的梳理和重构之后,将可以在子线程中运行的任务提交到子线程中执行,将之前比较耗时的任务分拆为多个子任务,从而尽可能的充分利用CPU的算力。
并且将启动阶段的任务都对接到了任务管理中统一调度,避免其它线程抢占CPU资源以及一些不必要的系统开销问题,完成了启动阶段线程的收敛。通过采取这些措施启动阶段的线程数明显降低,主线程因为子线程抢占CPU资源而导致的执行效率过低的问题也得到了解决。
但是这样做有一个特别需要注意的地方,对于那些短耗时的操作来说,可能造成负优化的效果,因为我们的最终目的是让应用的首帧画面尽快的加载出来,用户能够迅速地进入到MainActivity当中,但是不加考虑的将短耗时操作放入异步线程中,这些工作线程很有可能在MainActivity进行布局和渲染前就已经完成了自身使命,那么这样来看布局初始化前的绝对时间并没有减少,相反因为建立线程和切换线程等开销,造成了更多的时间消耗,所以这是一个特别需要注意的地方。
4.4 兜底与预加载
当任务延迟后,任务执行时机存在不确定性。这可能导致在业务需要时,一些初始化任务还没有执行完成的问题——任务管理提供了任务兜底机制。使用上面的API可以保证业务依赖的任务一定被执行,从而解决了因为“异步”与“延迟”可能给业务带来崩溃或者逻辑异常问题。
与兜底相对应的是对一些获取时间较长的数据、图片或者文件可以通过预加载的方式,保证用户能够第一时间看到。在Application启动时把首页需要的数据预先从本地取出并放置在内存中,当首页Activity打开时,直接从内存中获取数据,而不是从数据库中获取数据。
4.5 数据延迟加载
对在启动阶段主要设置了两个事件:广告展示事件和首页展示事件。按照业务执行的必要性,我们把启动阶段不必要执行的任务分别延迟到了这两个阶段之后执行。任务之间也按照之前梳理的依赖关系,分别设置了相应的依赖条件,解决了因被依赖任务被延迟未执行而导致的崩溃或业务异常问题。任务管理还提供了一些运行监测机制,能有效避免循环依赖问题的发生。由于任务管理的调度管理,这些任务在运行条件得到满足后会根据当前运行条件与设置的任务优先级等情况合理调度执行,不会出现大量任务同时并发执,从而导致页面卡顿的问题。
4.6 网络优化(服务端优化)
通常来说,一个App启动时需要做若干网络请求,包括数据上报、获取配置、拉取首屏内容、加载图片等等,而绝大多数App都是采用HTTP请求的方式来执行的,那么HTTP的性能就不得不关注。
整个连接的建立过程:首先是dns查询,需要一个RTT(RoundTrip Time)。其次是https连接过程,tcp握手需要一个RTT,而TLS握手需要两个RTT。假如网络情况ping需要100ms,那么在请求真正发起之前,dns+tcp+TLS就已经用掉了400ms(4个RTT),在这之后才能真正的发起请求(这些还需要一个RTT加上服务端的响应时间)。可以想象,如果每个网络请求都需要先经历这个过程,那样的速度是无法接受的。
一般服务器会将dns的ttl设置为60秒,因此在启动过程中只需要查询一次,60秒内其他请求就可以复用第一个请求的dns查询结果。那么就能够省下了一个RTT;万一结果不一致,再重新进行连接。
HTTP/1.1支持keep-alive,在一个请求结束后,tcp连接不会马上关闭,那么下一个请求可以复用该连接,直接省去4个RTT。
4.7 减少启动进程及通信
由于每个进程初始化时都会把Application执行一遍,导致任务重复执行,耗时较多,金融App在Application执行时,将进程分为主进程和其他进程,分别做不同的初始化流程,减少进程初始化消耗的时间。
4.8 Activity优化
前文提到Activity加载速度是很影响用户体验的,也是衡量一款App质量的重要指标之一,所以优化Activity加载速度至关重要。在金融App启动速度优化过程中对主要从以下五个方向对Activity进行优化。
1)优化合并Activity,减少启动时间。
2)耗时操作要进行异步处理,尽量在onCreate(Bundle)方法中减少耗时操作,在onResume()也不可进行过多操作。
3)布局文件优化
4)视图加载优化
5)另外,有时候我们会用到自定义控件,所以一定要进行绘制优化
4.9 其他技术应用
在京东金融App启动优化过程中还使用了其他技术,比如Multidex、文件重排、CPU锁频等技术,但因篇幅问题,本文不在详细赘述。
五、启动速度优化成果
通过两个月三次迭代完成了App启动速度优化工作, App启动速度在中端机型机型App启动速度从原来的2.4s提升到到1.2s,高端机型启动速度1s以内完成启动,启动速度实现了45%提升,初步达到预期结果。
启动优化工作是一个长期的工作,需要建立一套长效机制,要有一套准入规范、代码维护管控流程、以及版本性能测试与监控机制,这样才能够让App持续的”快”起来,用户才能够感觉“飞“起来。