一、前言随着项目版本的迭代,App的性能问题会逐渐暴露出来,良好的用户体验与性能息息相关。从本文开始,我将开启Android应用性能优化专题,从理论到实战,从入门到深挖,将性能优化实践到项目中,欢迎继续关注!然后我将从应用程序启动优化开始第一篇文章。根据实际案例,打造快如闪电的App启动速度。2.启动加速初识我们先看看Google的官方文档《Launch-Time Performance》(https://ldeveloper.android.com/topic/performance/launch-time.html)对应用程序启动优化的概述;应用程序启动分为冷启动、热启动、暖启动,最慢也是最有挑战性的启动是冷启动:无论是系统还是App本身都有更多的工作要从头开始!应用冷启动前,需要执行三个任务:加载并启动App;App启动后立即显示一个空白的Window;创建应用程序的过程;完成这三个任务后,会立即执行以下任务:创建App对象;启动主线程;创建启动的Activity对象;加载视图;布置画面;第一张图;而当App进程完成第一次绘制后,系统进程会将显示的BackgroundWindow替换为MainActivity,此时用户就可以使用App了。作为一个普通的应用程序,我们无法主动控制App进程的创建。可以优化的是Application、Activity的创建、回调的流程。同样,谷歌也给出了启动加速的方向:利用提前显示的Window快速显示一个界面,给用户一个快速反馈的体验;避免在启动时进行密集和繁重的初始化(Heavyappinitialization);定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。备注:方向1是治标不治本,只是表面上的快;方向2和3其实可以加快启动速度。接下来,我们将实际应用到项目中。3.切换启动加速主题按照官方文档说明:使用Activity的windowBackground主题属性为启动的Activity提供一个简单的drawable。LayoutXML文件:Manifest文件:这样启动的时候,首先会显示一个界面。这个界面就是Manifest中设置的Style。Activity加载完成后,我们再加载Activity的界面。在Activity的界面中,我们将主题重置为普通主题,从而产生快速的感觉。不过如上总结,这种方式并没有真正加速启动过程,而是通过交互体验优化了显示效果。注:截图同样来自官方文档《Launch-Time Performance》。4.避免启动加速的HeavyAppInitialization通过代码分析,我们可以得到App启动的业务流程图:本章重点介绍初始化部分:在Application和首屏Activity中,我们主要做:MultiDex和Tinker初始化最先执行;Application主要是初始化各种三方组件;项目中除听云外的所有三方组件都在Application的主线程中牵头初始化。这种初始化方式肯定是太重了:考虑在不阻塞主线程的情况下异步初始化三方组件;延迟部分三方组件的初始化;实际上,我们把所有的三方组件粗粒度的放到异步任务中,可能会出现在WorkThread中没有初始化但是已经在MainThread中使用的错误,所以建议延迟初始化直到在这种情况下使用它;而如何启动WorkThread也是有讲究的,下面会详细讨论这个话题。项目修改:在WorkThread中初始化友盟、Bugly、听云、GrowingIO、BlockCanary等组件;延迟地图定位、ImageLoader、自有统计等组件的初始化:地图和自有统计延迟4秒,应用已经打开;而ImageLoader因为调用关系不能异步延迟时间长,初始化从Application延迟到SplashActivity;而EventBus因为是在Activity中使用,所以必须在Application中初始化。注意:闪屏页面上的2秒停顿可用于将耗时操作延迟到此时间间隔内。5.诊断启动加速的问题在这一部分,我们实际上是定位耗时的操作。在开发阶段,我们一般使用BlockCanary或者ANRWatchDog来查找耗时操作。简单明了,但是我们无法得到每个方法的执行时间和更详细的对比信息。我们可以通过MethodTracing或DDMS获得更全面和详细的信息。启动应用,点击StartMethodTracing,应用启动后再次点击,会自动打开刚才操作记录的.trace文件,推荐使用DDMS查看,功能更方便全面。左边是具体发生的线程,右边是发生的时间轴,下面是具体发生的方法信息。注意两列:RealTime/Call(实际发生时间),Calls+RecurCalls/Total(发生次数);从上图我们可以得到以下信息:可以直观的看到MainThread的时间轴很长,说明大部分任务都在MainThread中执行;通过RealTime/Call的降序,可以看出程序中有些代码确实很耗时;在接下来的页面中,你可以看到一些第三方SDK也很耗时;即使是耗时的操作,只要在WorkThread中正确发生就没有问题。因此,我们需要确认这些方法的执行线程和发生的时机。如果这些操作发生在主线程上,可能不构成ANR发生的条件,但是卡顿是在所难免的!结合上一章App冷启动业务流程图中的业务操作和分析图,我们又可以看到代码:一些IO读取等耗时操作确实发生在主线程上。其实在traceview中点击执行函数的名字,不仅可以跟踪父类和子类的耗时方法,还可以看到它在哪个线程,以及方法执行中闪现的耗时界面时间线。分析到有些耗时操作发生在主线程,那么如果我们把这些耗时操作全部换成子线程,就万事大吉了?不!!不能靠异步来解决卡顿,错误的使用工程线程不仅不会改善卡顿,反而可能会加剧卡顿。是否需要开启工作线程需要根据性能瓶颈的具体根源进行分析,不能对症下药;而如何启动一个线程也是有学习的:Thread、ThreadPoolExecutor、AsyncTask、HandlerThread、IntentService等各有优缺点;例如,一般情况下,ThreadPoolExecutor比Thread更高效,优势明显,但是在特定场景下,Thread会在单个时间点上比ThreadPoolExecutor表现更好:ThreadPoolExecutor的开销明显高于Thread对于相同的对象创建;正确启动线程并不能包治百病,比如执行网络请求。创建线程池,在Application中正确创建线程池必然会降低启动速度;因此,延迟操作也是必不可少的。通过traceview的详细跟踪和代码的详细对比,发现卡顿的发生:一些数据库和IO操作发生在首屏Activity的主线程;在应用程序中创建了一个线程池;第一个屏幕Activity有密集的网络请求;workerthreads未设置使用优先级;信息未缓存,重复获取相同信息;进程问题:比如每次下载闪屏图片,当前使用情况;和其他细节:执行无用的旧代码;执行开发阶段使用的代码;执行重复逻辑;调用第三方SDK或Demo中的冗余代码;项目修改:1.将数据库和IO操作移至worker线程,并设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样worker线程最多可以获得10%的时间片,保证优先级主线程执行.2、梳理流程,延期执行;事实上,这一步对于加速项目的启动是最有效的。通过流程梳理,发现部分流程调用时机过早、错误等,例如:update等操作不需要在首屏显示前调用,造成资源竞争;调用IOS做的switch以避免审计,导致网络请求密集;自己统计在Application调用中创建固定数量5的线程池,导致资源竞争。在上面traceview函数描述图的最后一行,可以看到数字12被执行了5次,耗时排名靠前;这里线程池的创建是必要的,但被推迟了。修改广告闪屏逻辑,下次生效。3.其他优化;删除已执行的无用旧代码;去除在开发阶段使用但在线执行的代码;去除重复的逻辑执行代码;去除调用第三方SDK或demo的冗余代码;信息缓存,普通信息只在第一次获取,然后从缓存中获取;项目是多进程架构,只在主进程执行Application的onCreate();通过以上三步和三方组件的优化:在Application和首屏Activity回调期间,主线程不会处于活跃状态,会耗时,争抢资源等。此外,还涉及布局优化、内存优化等一些技术。因为应用的冷启动一般不是瓶颈点,这里就不详细讨论了,可以根据实际项目来处理。六、对比效果:使用adb命令统计应用的启动时间:adbshellamstart-WfirstscreenActivity。相同条件下使用MX3和Nexus6P,启动5次,比较优化前和优化后的启动时间;优化前:MX3Nexus6P优化后:MX3Nexus6P对比:MX3提升35%Nexus6P提升39%命令含义:ThisTime:上次激活的Activity启动时间;TotalTime:自身所有Activity的启动时间;WaitTime:ActivityManagerService启动App的Activity的总时间(包括当前Activity的onPause()和自身Activity的启动)。七、问题:1、可以继续优化吗?该项目使用Retrofit网络请求库和FastConverterFactory作为Json解析器。在TraceView中创建FastConverterFactory也很耗时。考虑用GsonConverterFactory替换它。但由于类的继承关系短时间内无法直接替换,暂时留作优化点;可以考虑根据实际情况在启动时将部分接口合并为一个,减少网络请求次数,降低频率;只保留一个功能相同的组件,例如:友盟、GrowingIO、自有统计等功能重复;使用ReDex进行优化;实验Redex发现Apk体积确实变小了一点,但是启动速度没有变化,可能需要继续研究。2.异步和延迟初始化和运行的基础?注意:并不是每个组件的初始化和操作都可以异步或延迟;能否确定取决于组件的调用关系和自身项目的具体业务需求。保证一个规则:能异步的就异步,不能异步的尽量延迟。让应用程序先启动,然后再运行。3.通用应用启动加速例程?使用主题快速显示界面;异步初始化组件;梳理业务逻辑,延迟组件和操作的初始化;正确使用线程;去除无用代码、重复逻辑等。4.其他启动速度提升35%并不代表之前的代码有问题。从业务角度来看,代码没有错误,已经满足业务需求。但在注重速度的启动阶段,忽略细节可能会导致性能瓶颈。在开发过程中,使用TraceView分析核心模块和应用阶段,如启动,尽早发现瓶颈。
