爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

首席大胸器 发布于 01/14 11:53
阅读 78
收藏 0

本文由爱奇艺技术团队原创分享,原题《爱奇艺Android客户端启动优化与分析》。

1、引言

互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Android APP而言,要求更加严格,如果系统无响应时间超过5秒,便会出现ANR,APP可能会被强制关闭,因此,启动时间作为一个重要的性能指标,关系着用户的第一体验。

爱奇艺安卓APP非常重视启动速度的优化,本文将从启动过程、启动时间测量、启动优化、以及后续监控等方面分享我们在启动优化方面积累的经验。

相关文章:

移动端IM实践:Android版微信如何大幅提升交互性能(一)

移动端IM实践:Android版微信如何大幅提升交互性能(二)

移动端IM实践:iOS版微信界面卡顿监测方案

微信团队原创分享:Android内存泄漏监控和优化技巧总结

美图App的移动端DNS优化实践:HTTPS请求耗时减小近半

(本文同步发布于:http://www.52im.net/thread-2221-1-1.html

2、启动模式

要准确的测量APP的启动时间,首先我们要了解APP整个启动过程。

启动过程,一般可以分为以下三类:

从上图可以看出,启动过程中,Cold的模式下,生命周期中做的事情最多,启动的时间最长。因此,我们以冷启动来衡量APP启动时间。

那么启动过程中,如何判断哪些生命周期影响启动速度呢?请继续往下读。

3、启动过程

我们知道,APP的启动和运行,就是Linux系统创建进程和组件对象,并在UI线程中处理组件消息的过程。

启动过程图:

App的启动过程,可以划分为三个阶段,下面就各个阶段进行详细讲解。

3.1 创建进程

当APP启动时,如果当前app的进程不存在,便会创建新的进程;App主进程启动后,如果启动某个组件,并且该组件设置了android:process属性,组件所运行的进程不存在,也会创建新的进程。

需要注意的是,如果在启动阶段,初始化的组件中,包含了多个进程,便会创建多次进程,BindApplication操作也会重复执行多次

3.2 创建UI线程及Handler

进程创建后,会通过反射,执行ActivityThread入口函数,创建Handler,并在当前线程中prepareMainLooper,并在Handler中接收组件的消息。

我们来看一下Handler中处理的消息:

1)LAUNCH_ACTIVITY:启动,执行Activity;

2)RESUME_ACTIVITY:恢复Activity;

3)BIND_APPLICATION,启动app;

4)BIND_SERVICE:Service创建, onBind;

5)LOW_MEMORY:内存不足,回收后台程序。

sMainThreadHandler中,处理的消息很多,这里只罗列了,可能在启动阶段可能会执行的操作, 这些操作都是运行在Main Thread中,对启动而言,属于阻塞性的。

Activity生命周期,自然需要在启动阶段执行,但,对于Service的创建,Trim_memory回调,广播接收等操作,就需要重点考虑,其操作耗时性。

3.3 Activity运行及绘制

前两个过程,创建进程和UI线程及Handler,都是由系统决定的,对APP开发者而言,并不能控制其执行时间,在本阶段,执行BindApplication,和Acitivity生命周期,都是可以由开发者自定义。

Activity执行到onResume之后,会执行至ViewRootImpl,执行两次performTraversals,第二次traversal操作中,会执行performDraw操作,同时通知RenderThread线程执行绘制。

从启动的三个阶段,我们可以看出,启动启动时间的长短的决定因素在于:主线程中所做事情消耗的时间的多少。

所以:我们的优化工作主要集中在,排查主线程中耗时性的工作,并进行合理的优化。

Android手机,系统的资源是有限的,过多的异步线程,会抢占CPU,导致主线程执行时间片间隔增大。同样的,内存消耗状态,GC频率,也会影响启动的时间。

4、分析及测量

通过上述的源码的解读,我们已经了解了启动过程,以及可能引起启动过慢的原因。接下来介绍一些常用的分析手段及时间测量方法。

我们的启动分析工具主要使用SysTrace,具体的使用方法请参考官网文档:https://developer.android.com/studio/command-line/systrace

▲ Andriod上的各种各样分析工具,请自行选用

4.1 SysTrace分析技巧

【4.1.1、UI Thread 颜色显示】

绿色:Running

白色:Sleeping

棕色:Uninterruptible Sleep

橙色:Uninterruptible Sleep - Block I/O

其中10ms以内的,较短时间的Sleeping状态,不用关注,可能是由于CPU调度的时间片分配间隔引起的;较长时间的Block I/O和Sleep状态,可以确定有阻塞启动的逻辑在这个阶段运行,需要进一步对代码进行分析定位。

【4.1.2、查看CPU状态及线程运行时长】

查看CPU占用状态:

线程执行:

通过该阶段密集程度,反映出CPU占用率,也能在一定程度上反映出该阶段执行时间被阻塞情况;线程执行情况统计,可以查看线程执行时间排名,对执行时间较长的子线程进行优化。

4.2 SysTrace启动时间

在SysTrace图中,UI Thread中包含了bindApplication,activityStart,traversal等操作,RenderThread中包含DrawFrame等操作。这些TAG节点是源码已经添加的,可参考#3.2中介绍。

Trace上启动时间:

从bindApplication至第二次traversal完成,可认为UI第一次绘制完成,启动完成。选中开始点和结束点,可以查看过程消耗的时间。

4.3 adb shell am start -W

在统计APP启动时间时,系统为我们提供了adb命令,可以输出启动时间

TotalTime: 

表示新应用启动的耗时,包括新进程的启动和 Activity 的启动,但不包括前一个应用 Activity pause 的耗时。

系统在绘制完成后,ActivityManagerService会回调该方法,统计时间不如SysTrace准确,但是能够方便我们通过脚本多次启动测量TotalTime,对比版本间启动时间差异。

4.4 埋点

通过APP启动生命周期中,关键位置加入时间点记录,达到测量目的。

4.5 录屏

录屏方式收集到的时间,更接近于用户的真实体感。

5、优化总结

为了让用户在进入APP之后,更快更流畅的使用服务,所以会在启动过程中,提前对一些基础库和组建进行初始化操作,这就意味着系统有限的资源会被抢占,影响启动时间。启动时间的优化,是一个平衡性能和体验的过程。

通过Systrace工具分析,我们发现爱奇艺爱奇艺安卓APP启动过程中一些问题,接下来,我们就结合具体的业务实践,进行启动问题进行优化。

5.1 区分进程初始化Application

由第3章我们了解到,对于一个app而言,App内组件可以运行在不同的进程之中。

举个例子: 

一个APP拥有主进程,插件进程,下载进程三个进程,会在启动阶段创建相应的组件,但只有一个QYApplication继承自系统Application,创建三次进程,QYApplication中attach(),onCreate()方法都会被执行三次。

每个进程说需要初始化的内容肯定是不一样的,所以,为了防止资源的浪费,我们需要区分进程,初始化Appcation.

成果:对多进程应用而言,通过对初始化内容进行梳理,合理区分初始化,会大幅减少内存和CPU占用。

5.2 异步处理耗时任务

子线程处理耗时任务,主线程做的事情越少,越早进入Acitivity绘制阶段,界面越早展现。

注意:

1)不在主线程做耗时任务,如文件,网络等;

2)启动阶段初始化任务,尽量在异步线程处理;

3)主线程,不用等待或者依赖于子线程任务。

进一步优化:可以自建线程池,维持一定线程个数,管理任务队列。

5.3 防止多线程抢占CPU

Android系统资源有限,特别是CPU资源,理论上来说,UI线程执行的任务,也无法保证一直被调度状态,当并发的线程数过多,UI线程时间片会更短,从而导致启动时间被变慢。

下面罗列一些常见,容易造成CPU被抢占的场景:

成果:通过对执行时间较久,执行频率的业务进行优化,将CPU占有率维持在合理的程度,会大幅减少启动时间,减少300ms以上。

5.4 系统API使用

部分系统的API使用是阻塞性的,文件很小可能无法感知,当文件过大,或者使用频繁时,可能造成阻塞。

例如:

1)SharedPreference.Editor提交操作:

  - commit方法属于属于阻塞性质API,建议使用apply;

  - 此外,我们知道,SP文件的存储是一个XML文件,以key-value形式存储,当业务过多时,需要拆分为多个文件存储,防止文件过大,出现读取耗时及ANR;

  - 进一步优化,可对启动阶段,频繁的SP操作在内存中,统一提交。

2)AssetManager.open操作: 

Android开发中,我们有时会将资源文件放在assets目录中,然后使用open操作读取文件,如果文件过大,需要在异步线程中执行。

成果:随着业务量日积月累,正常的系统API的使用,也可能出现问题,通过排除,可减少50-100ms。

5.5 精简布局

布局的复杂程度,直接影响绘制的时间。

举个例子:

在启动过程中,会有需要大的背景图,只有第一次安装时使用,后续属性设置为android:visibility="gone",但是,虽然设置了gone属性,不会显示,但依旧会被解析。

建议:

1)减少布局层次;

2)无用资源使用ViewStub,使用时加载。

成果:启动阶段的布局较简单,通过优化背景图片的加载,减少50-100ms。

5.6 Service延后初始化

App启动中过程中,经常进行Service初始化操作,由于Service使用一般不涉及界面,可能会认为初始化生命周期不在主线程中,其实不然,在3.2的启动过程源码介绍中讲到,Service的生命周期,也属于主线程Handler接收的Message之一。

建议:Service生命周期中,注意逻辑执行时间性能优化,初始化尽量延后。

成果:取决于初始化Service的生命周期执行时间,可减少200ms以上。

5.7 将任务delay至首页绘制完成后

对于APP首页展示不需要的初始化逻辑,可延后至首页绘制完成后初始化。

注意:需要post两次才能保证在第一次绘制之后显示,因为,系统绘制会执行两次Performtraversal。

进一步优化:可将业务逻辑的初始化划分为,首页绘制后,5s,10s,20s三个阶段分别初始化,防止首页绘制执行任务过多造成掉帧。

成果:释放绘制阶段的CPU,可将复杂的绘制提前200ms以上。

6、性能监控

稳定的用户体验依赖于持续的监控,爱奇艺为监控启动性能建立了一套监控体系、测试、工具、开发等几个团队从不同的纬度搭建不同的监控方案。

监控方案如下:

1)测试:录屏,从用户的真实体验角度,获取最准确的启动时间;

2)实时监控:通过埋点,大数据采样投递获取真实线上环境数据,从地域,时间,机型,app版本,系统版本等各个纬度对启动时间进行监控;

3)脚本测试:通过对脚本,对同一收集多次启动数据进行收集,通过不同版本间的对比,监控启动时间的变化情况。

7、SysTrace扩展

SysTrace通过TAG节点可以清晰展现,启动过程以及方法执行时间,但是,从发现问题,然后通过节点去定位问题,是一件很繁琐的工作,如果你们工程编译又比较慢,简直让人崩溃。

比如:自动化TAG注入。

在Android工程编译的过程中,指定class,在方法前后,自动化插入Trace节点,统计方法执行时间。

流程:

1)在编译的过程中,插入自定义Task任务;

2)读取配置文件,文件中包含了需要注入java文件名和路径名和method;

3)找到需要注入的class文件,然后通过ASM改变字节码,方法前后,插入自定义自定义方法。

通过工具的操作,能够做到不用修改原有工程文件,自动在打包时注入TAG节点和逻辑代码,配置文件可以循环利用,提高分析效率,节能环保。

8、优化成果

启动时间,由于不同的机型性能同,Android系统版本不同,同一APP版本启动时间,相差很大,所以统计一般以同一手机,不同版本做比较,尽量保证手机状态一致。

SysTrace手机优化时间对比:

脚本多次启动时间收集对比:

经过多个版本的持续优化,有无广告两种不同的场景下,启动时间分别减少40%和35%,启动速度得到了较大的提升。

9、本文小结

启动时间的优化和监控,是一项长期的任务,需要对异常的情况进行分析,对可能造成阻塞的代码逻辑进行合理的优化,非常感谢各个业务团队支持和配合。

以上就是全部启动时间优化相关的内容,谢谢大家能够阅读到这里,如果有更好的建议,欢迎交流!

附录:更多移动端高级开发技术分享

全面了解移动端DNS域名劫持等杂症:技术原理、问题根源、解决方案等

金蝶随手记团队分享:还在用JSON? Protobuf让数据传输更省更快(原理篇)

金蝶随手记团队分享:还在用JSON? Protobuf让数据传输更省更快(实战篇)

腾讯技术分享:社交网络图片的带宽压缩技术演进之路

通俗易懂:基于集群的移动端IM接入层负载均衡方案分享

QQ音乐团队分享:Android中的图片压缩技术详解(上篇)

QQ音乐团队分享:Android中的图片压缩技术详解(下篇)

腾讯原创分享(一):如何大幅提升移动网络下手机QQ的图片传输速度和成功率

腾讯原创分享(二):如何大幅压缩移动网络下APP的流量消耗(上篇)

腾讯原创分享(三):如何大幅压缩移动网络下APP的流量消耗(下篇)

基于社交网络的Yelp是如何实现海量用户图片的无损压缩的?

腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(图片压缩篇)

腾讯技术分享:腾讯是如何大幅降低带宽和网络流量的(音视频技术篇)

最火移动端跨平台方案盘点:React Native、weex、Flutter

iPhone X 的 UI界面适配官方指南!》

新浪微博技术分享:微博短视频服务的优化实践之路

全面掌握移动端主流图片格式的特点、性能、调优等

迈向高阶:优秀Android程序员必知必会的网络基础

HTTPS时代已来,打算更新你的HTTP服务了吗?

移动端APP的日志上报机制的优化实践

移动端网络优化之HTTP请求的DNS优化

伪即时通讯:分享滴滴出行iOS客户端的演进过程

Android版微信从300KB到30MB的技术演进(PPT讲稿) [附件下载]

微信团队原创分享:Android版微信从300KB到30MB的技术演进

Android程序员的痛你永远不懂(一):Bitmap到底占用多大内存?

Android程序员的痛你永远不懂(二):如何减少Bitmap内存占用?

Android反编译利器APKDB:没有美工的日子里继续坚强的撸

全面总结iOS版微信升级iOS9遇到的各种“坑”

微信团队原创资源混淆工具:让你的APK立减1M

微信团队原创Android资源混淆工具:AndResGuard [有源码]

Android版微信安装包“减肥”实战记录

iOS版微信安装包“减肥”实战记录

iOS端移动网络调优的8条建议

微信“红包照片”背后的技术难题

移动端IM实践:iOS版微信小视频功能技术方案实录

移动端IM实践:iOS版微信的多设备字体适配方案探讨

爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结

>> 更多同类文章 ……

(本文同步发布于:http://www.52im.net/thread-2221-1-1.html

加载中
返回顶部
顶部