内容简介:本文由爱奇艺技术团队原创分享,原题《爱奇艺Android客户端启动优化与分析》。互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Android APP而言,要求更加严格,如果系统无响应时间超过5秒,便会出现ANR,APP可能会被强制关闭,因此,启动时间作为一个重要的性能指标,关系着用户的第一体验。爱奇艺安卓APP非常重视启动速度的优化,本文将从启动过程、启动时间测量、启动优化、以及后续监控等方面分享我们在启动优化方面积累的经验。
本文由爱奇艺技术团队原创分享,原题《爱奇艺Android客户端启动优化与分析》。
1、引言
互联网领域里有个八秒定律,如果网页打开时间超过8秒,便会有超过70%的用户放弃等待,对Android APP而言,要求更加严格,如果系统无响应时间超过5秒,便会出现ANR,APP可能会被强制关闭,因此,启动时间作为一个重要的性能指标,关系着用户的第一体验。
爱奇艺安卓APP非常重视启动速度的优化,本文将从启动过程、启动时间测量、启动优化、以及后续监控等方面分享我们在启动优化方面积累的经验。
相关文章:
《 移动端IM实践:Android版微信如何大幅提升交互性能(一) 》
《 移动端IM实践:Android版微信如何大幅提升交互性能(二) 》
《 微信团队原创分享: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 》
《 迈向高阶:优秀Android程序员必知必会的网络基础 》
《 Android版微信从300KB到30MB的技术演进(PPT讲稿) [附件下载] 》
《 微信团队原创分享:Android版微信从300KB到30MB的技术演进 》
《 Android程序员的痛你永远不懂(一):Bitmap到底占用多大内存? 》
《 Android程序员的痛你永远不懂(二):如何减少Bitmap内存占用? 》
《 Android反编译利器APKDB:没有美工的日子里继续坚强的撸 》
《 微信团队原创Android资源混淆工具:AndResGuard [有源码] 》
《 iOS端移动网络调优的8条建议 》
《 微信“红包照片”背后的技术难题 》
《 移动端IM实践:iOS版微信的多设备字体适配方案探讨 》
《 爱奇艺技术分享:爱奇艺Android客户端启动速度优化实践总结 》
>> 更多同类文章 ……
(本文同步发布于: http://www.52im.net/thread-2221-1-1.html )
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。