1简介互联网领域有一个八秒规则。如果一个网页打开时间超过8秒,超过70%的用户会放弃等待。对于AndroidAPP,要求比较严格,如果超过5秒系统没有响应,就会出现ANR,APP可能会被强制关闭。因此,启动时间是一项重要的性能指标,关系到用户的最佳体验。爱奇艺安卓APP非常重视启动速度的优化。本文将从启动流程、启动时间实测、启动优化、后续监控等方面分享我们积累的启动优化经验。2启动方式要准确衡量APP的启动时间,首先要了解APP的整个启动过程。启动过程一般可以分为以下三类:从上图可以看出,在启动过程中,在Cold模式下,生命周期中做的事情最多,启动-开机时间最长。因此,我们使用冷启动来衡量APPStartTime。在启动过程中,如何判断是哪个生命周期影响了启动速度呢?3、启动过程我们知道,APP的启动运行就是在Linux系统中创建进程和组件对象,在UI线程中处理组件消息的过程。启动过程图:App启动过程可以分为三个阶段:3.1创建进程APP启动时,如果当前app进程不存在,则会创建一个新进程;App主进程启动后,如果启动了一个组件,并且该组件设置了android:process属性,则该组件运行的进程不存在,会创建一个新的进程。需要注意的是,如果初始化组件在启动阶段包含多个进程,则会创建多个进程,BindApplication操作会重复多次。执行ActivityThread入口函数,创建Handler,并在当前线程中prepareMainLooper,在Handler中接收组件消息。我们看一下Handler中处理的消息:LAUNCH_ACTIVITY,启动,执行ActivityRESUME_ACTIVITY,恢复ActivityBIND_APPLICATION,启动appBIND_SERVICE,Service创建,onBindLOW_MEMORY,内存不足,回收后台程序sMainThreadHandler,处理了很多消息,这里只是列出启动阶段可能执行的操作,这些操作都是在MainThread中运行的,对于启动来说,是有阻塞的。Activity生命周期自然需要在启动阶段执行。但是需要考虑Service创建、Trim_memory回调、广播接收等操作,它们的操作比较耗时。3.3Activity运行和绘制的前两个过程,creatingprocess,UIthread和Handler都是由系统决定的。对于APP开发者来说,他们无法控制自己的执行时间。这个阶段执行BindApplication和Acitivity生命周期。,可由开发商定制。Activity执行到onResume后,会执行到ViewRootImpl,执行两次performTraversals,在第二次遍历操作时,会执行performDraw操作,通知RenderThread线程进行绘制。从启动的三个阶段我们可以看出,启动时间的长短取决于主线程做事情的时间长短。因此,我们的优化工作主要集中在检查主线程中的耗时工作,并进行合理的优化。对于Android手机,系统资源有限,过多的异步线程会抢占CPU,导致主线程的执行时间片间隔增加。同样,内存消耗情况,GC频率,也会影响启动时间。4分析与测量通过以上源码的解读,我们了解了启动过程以及启动慢的可能原因。接下来介绍一些常用的分析方法和时间测量方法。启动分析工具,主要使用SysTrace。具体使用方法请参考官网文档https://developer.android.com/studio/command-line/systrace。4.1SysTrace分析技巧4.1.1UI线程颜色显示Green:RunningWhite:SleepingBrown:UninterruptibleSleepOrange:UninterruptibleSleep-BlockI/O其中Sleeping状态短于10ms,不用注意,可能是由于对CPU调度是时间片分配间隔造成的;长时间的BlockI/O和Sleep状态可以确认阻塞启动的逻辑在这个阶段运行,需要对代码做进一步的分析和定位。4.1.2查看CPU状态和线程运行时间查看CPU占用状态:线程执行:通过该阶段的强度,可以反映CPU使用率,一定程度上阻塞了该阶段的执行时间;线程执行统计,可以查看线程执行时间排行,对执行时间长的子线程进行优化。4.2SysTrace启动时间在SysTrace图中,UIThread包括bindApplication、activityStart、traversal等操作,RenderThread包括DrawFrame等操作。这些TAG节点已经添加到源码中,请参考#3.2中的介绍。Trace上的启动时间:从bindApplication到第二次遍历完成,可以认为UI的第一次绘制完成,启动完成。选择起点和终点可以查看流程消耗的时间。4.3adbshellamstart-W在统计APP启动时间时,系统为我们提供了adb??命令,可以输出启动时间TotalTime:表示新应用的启动耗时,包括新进程和活动的启动,但不包括包括之前一个使用Activity暂停的耗时系统绘制完成后,ActivityManagerService都会回调这个方法。统计时间没有SysTrace那么准确,但是方便我们通过多次脚本启动来测算TotalTime,比较各个版本的启动时间差异。4.4埋点在APP启动的生命周期中,将关键位置添加到时间点记录中,以达到测量的目的。4.5录屏录屏方式采集的时间更接近用户的真实体感。5优化为了让用户进入APP后能更快更流畅的使用服务,在启动过程中会提前初始化一些基础库和组件,这意味着系统有限的资源会被抢占,影响启动时间。启动时间的优化是一个平衡性能和体验的过程。通过Systrace工具的分析,我们发现爱奇艺iQIYIAndroidAPP在启动过程中存在一些问题。接下来,我们结合具体的业务实践,对启动问题进行优化。5.1区分进程初始化应用程序我们从#3中了解到,对于一个应用程序,应用程序内的组件可以运行在不同的进程中。例如:一个APP有3个进程:主进程、插件进程、下载进程,在启动阶段会创建相应的组件,但只有一个QYApplication继承自系统Application,创建了3个进程。在QYApplication中,attach()、onCreate()方法会被执行3次。每个进程都说需要初始化的内容肯定是不一样的。所以,为了防止资源浪费,我们需要区分进程,初始化Appcation。结果:对于多进程应用,通过整理初始化内容,可以合理区分初始化。它将大大降低内存和CPU使用率。5.2异步处理耗时任务子线程处理耗时任务,主线程做的越少,越早进入Acitivity绘图阶段,越早显示界面。注意:不要在主线程中做耗时任务,如文件、网络等启动阶段的初始化任务,尽量在异步线程中处理主线程,不等待也不依赖子线程任务进一步优化:你可以建立自己的线程池来维护一定数量的线程,来管理任务队列。5.3防止多线程抢占CPUAndroid系统资源是有限的,尤其是CPU资源。理论上,UI线程执行的任务不能保证一直被调度。当并发线程过多时,UI线程的时间片会变短,从而导致启动时间变慢。以下是一些常见的容易造成CPU抢占的场景:结果:通过对执行时间长、执行频率高的业务进行优化,将CPU占用保持在合理水平,启动时间将大幅减少300ms以上。5.4系统API使用部分系统的API使用是阻塞的,文件小的时候可能感知不到文件。当文件过大或使用频繁时,可能会造成阻塞。例如:SharedPreference.Editor提交操作:commit方法是一个阻塞API,推荐使用apply。另外,我们知道SP文件的存储是一个XML文件,以key-value的形式存储。当业务过多时,需要拆分成多个文件存储,防止文件过大,读取耗时,ANR。为了进一步优化,可以在启动阶段将频繁的SP操作统一提交到内存中。AssetManager.open操作:在Android开发中,我们有时会把资源文件放在assets目录下,然后使用open操作来读取文件。如果文件太大,需要在异步线程中执行。结果:随着业务量的积累,正常的系统API使用也可能出现问题。通过消除它们,可以减少50-100ms。5.5简化布局布局的复杂程度直接影响绘图时间。比如在启动过程中,会出现一个大背景图,只在第一次安装时使用,后面的属性设置为android:visibility="gone",但是虽然设置了gone属性,但是会不被显示。但它仍然会被解析。建议:使用ViewStub减少布局层面的无用资源,使用时加载。结果:启动阶段的布局比较简单。通过优化背景图片加载,减少50-100ms。5.6Service延迟初始化在App启动过程中,经常会进行Service初始化操作。由于Service的使用一般不涉及接口,所以你可能会认为初始化生命周期不在主线程中。事实上,并非如此。在3.2的启动流程源码介绍中,Service的生命周期也属于主线程Handler收到的Messages之一。建议:在Service生命周期中,注意逻辑执行时间的性能优化,尽量延迟初始化。结果:根据初始化Service的生命周期执行时间,可以减少200ms以上。5.7延迟任务,直到绘制主页。对于APP首页展示不需要的初始化逻辑,可以延迟到首页绘制完成初始化。注意:需要post两次,保证在第一次绘图后显示,因为系统绘图会执行两次Performtraversal。进一步优化:业务逻辑的初始化分为首页绘制后5s、10s、20s三个阶段,防止首页绘制执行任务过多导致掉帧。成果:绘图阶段释放CPU,复杂绘图提前200ms以上。6.监控稳定的用户体验依赖于持续的监控。爱奇艺建立了启动性能监控系统。包括测试、工具、开发在内的多个团队,从不同的纬度构建不同的监控方案进行测试:屏幕录制,从用户真实体验的角度获取最准确的启动时间。实时监控:通过埋点和大数据采样下发获取真实在线环境数据,从地域、时间、机型、APP版本、系统版本等多个维度监控开机时间。脚本测试:通过脚本,采集同一个集合的多个启动数据,通过不同版本之间的对比,监控启动时间的变化。7SysTrace扩展SysTrace可以通过TAG节点清晰的显示启动过程和方法执行时间。但是,发现问题,然后通过节点定位问题,是一件非常繁琐的工作。如果你的项目编译速度很慢,那简直是烦人。坍塌。7.1自动TAG注入在Android工程编译过程中,指定类,在方法前后自动插入Trace节点,统计方法的执行时间。流程:在编译过程中,插入一个自定义的Task任务,读取配置文件,里面有需要注入的java文件名、路径名和方法,找到需要注入的class文件,然后更改字节码通过ASM,在方法前后,插入自定义方法,通过工具的操作,可以在不修改原有工程文件的情况下,在打包时自动注入TAG节点和逻辑代码。配置文件可循环使用,提高分析效率,节能环保。8优化结果的启动时间,由于不同机型性能相同,Android系统版本不同,同一APP版本启动时间差异较大,所以统计一般都是与同款手机对比版本不同,尽量保证手机状态一致。SysTrace手机端优化时间对比:脚本多启动时间采集对比:经过多个版本的不断优化,在有广告和无广告两种不同场景下,启动时间分别减少了40%和35%,启动速度有了很大的提升改进了。9小结启动时间的优化和监控是一项长期的工作。需要分析异常情况,合理优化可能造成阻塞的代码逻辑。非常感谢各业务团队的支持与配合。
