android性能优化之启动加速35%

嘴巴吃糖了 嘴巴吃糖了     2023-03-09     573

关键词:

一、前言

随着项目版本的迭代,App的性能问题会逐渐暴露出来,而好的用户体验与性能表现紧密相关,从本篇文章开始,我将开启一个Android应用性能优化的专题,从理论到实战,从入门到深挖,手把手将性能优化实践到项目中,欢迎持续关注!
那么第一篇文章我就从应用的启动优化开始,根据实际案例,打造闪电般的App启动速度。

二、初识启动加速

应用的启动分为冷启动、热启动、温启动,而启动最慢、挑战最大的就是冷启动:系统和App本身都有更多的工作要从头开始! 应用在冷启动之前,要执行三个任务:

  1. 加载启动App;
  2. App启动之后立即展示出一个空白的Window;
  3. 创建App的进程;

而这三个任务执行完毕之后会马上执行以下任务:

  1. 创建App对象;
  2. 启动Main Thread;
  3. 创建启动的Activity对象;
  4. 加载View;
  5. 布置屏幕;
  6. 进行第一次绘制;

而一旦App进程完成了第一次绘制,系统进程就会用Main Activity替换已经展示的Background Window,此时用户就可以使用App了。

[图片上传失败…(image-df49a0-1640595066640)]

作为普通应用,App进程的创建等环节我们是无法主动控制的,可以优化的也就是Application、Activity创建以及回调等过程

同样,Google也给出了启动加速的方向

  1. 利用提前展示出来的Window,快速展示出来一个界面,给用户快速反馈的体验;
  2. 避免在启动时做密集沉重的初始化(Heavy app initialization);
  3. 定位问题:避免I/O操作、反序列化、网络操作、布局嵌套等。

备注:方向1属于治标不治本,只是表面上快;方向2、3可以真实的加快启动速度。 接下来我们就在项目中实际应用。

三、启动加速之主题切换

按照官方文档的说明:使用Activity的windowBackground主题属性来为启动的Activity提供一个简单的drawable。 Layout XML file:

[图片上传失败…(image-e2b87e-1640595066640)]

Manifest file:

[图片上传失败…(image-967a31-1640595066640)]


这样在启动的时候,会先展示一个界面,这个界面就是Manifest中设置的Style,等Activity加载完毕后,再去加载Activity的界面,而在Activity的界面中,我们将主题重新设置为正常的主题,从而产生一种快的感觉。不过如上文总结这种方式其实并没有真正的加速启动过程,而是通过交互体验来优化了展示的效果。

四、启动加速之Avoid Heavy App Initialization

通过代码分析我们可以得到App启动的业务工作流程图:

[图片上传失败…(image-a48ae6-1640595066640)]

这一章节我们重点关注初始化的部分:在Application以及首屏Activity中我们主要做了:

  • MultiDex以及Tinker的初始化,最先执行;关于MultiDex的优化本文不再赘述,参考我之前Multidex的系列文章
  • Application中主要做了各种三方组件的初始化;

项目中**除听云之外其余所有三方组件都抢占先机,在Application主线程初始化。**这样的初始化方式肯定是过重的:

  • 考虑异步初始化三方组件,不阻塞主线程;
  • 延迟部分三方组件的初始化;实际上我们粗粒度的把所有三方组件都放到异步任务里,可能会出现WorkThread中尚未初始化完毕但MainThread中已经使用的错误,因此这种情况建议延迟到使用前再去初始化;
  • 而如何开启WorkThread同样也有讲究,这个话题在下文详谈。

项目修改:

  1. 将友盟、Bugly、听云、GrowingIO、BlockCanary等组件放在WorkThread中初始化;
  2. 延迟地图定位、ImageLoader、自有统计等组件的初始化:地图及自有统计延迟4秒,此时应用已经打开;而ImageLoader 因为调用关系不能异步以及过久延迟,初始化从Application延迟到SplashActivity;而EventBus因为再Activity中使用所以必须在Application中初始化。

注意:闪屏页的2秒停留可以利用,把耗时操作延迟到这个时间间隔里。

五、启动加速之Diagnosing The Problem

本节我们实际定位耗时的操作,在开发阶段我们一般使用BlockCanary或者ANRWatchDog找耗时操作,简单明了,但是无法得到每一个方法的执行时间以及更详细的对比信息。我们可以通过Method Tracing或者DDMS来获得更全面详细的信息。 启动应用,点击 Start Method Tracing,应用启动后再次点击,会自动打开刚才操作所记录下的.trace文件,建议使用DDMS来查看,功能更加方便全面。

[图片上传失败…(image-b61ad7-1640595066640)]

左侧为发生的具体线程,右侧为发生的时间轴,下面是发生的具体方法信息。注意两列:Real Time/Call(实际发生时间),Calls+RecurCalls/Total(发生次数);上图我们可以得到以下信息:

  • 可以直观看到MainThread的时间轴很长,说明大多数任务都是在MainThread中执行;
  • 通过Real Time/Call 降序排列可以看到程序中的部分代码确实非常耗时;
  • 在下一页可以看出来部分三方SDK也比较耗时;**

即便是耗时操作,但是只要正确发生在WorkThread就没问题。因此我们**需要确认这些方法执行的线程以及发生的时机。这些操作如果发生在主线程,可能不构成ANR的发生条件,但是卡顿是再算难免的!**结合上章节图App冷启动业务工作流程图中业务操作以及分析图,再次查看代码我们可以看到:部分耗时操作例如IO读取等确实发生在主线程。事实上在traceview里点击执行函数的名称不仅可以跟踪到父类及子类的方法耗时,也可以在方法执行时间轴中看到具体在哪个线程以及耗时的界面闪动。

分析到部分耗时操作发生在主线程,那我们把耗时操作都改到子线程是不是就万事大吉了?非也!!

  • 卡顿不能都靠异步来解决,错误的使用工程线程不仅不能改善卡顿,反而可能加剧卡顿。是否需要开启工作线程需要根据具体的性能瓶颈根源具体分析,对症下药,不可一概而论;
  • 而如何开启线程同样也有学问:Thread、ThreadPoolExecutor、AsyncTask、HandlerThread、IntentService等都各有利弊;例如通常情况下ThreadPoolExecutor比Thread更加高效、优势明显,但是特定场景下单个时间点的表现Thread会比ThreadPoolExecutor好:同样的创建对象,ThreadPoolExecutor的开销明显比Thread大;
  • 正确的开启线程也不能包治百病,例如执行网络请求会创建线程池,而在Application中正确的创建线程池势必也会降低启动速度;因此延迟操作也必不可少。

通过对traceview的详细跟踪以及代码的详细比对,我发现卡顿发生在

  • 部分数据库及IO的操作发生在首屏Activity主线程;
  • Application中创建了线程池;
  • 首屏Activity网络请求密集;
  • 工作线程使用未设置优先级;
  • 信息未缓存,重复获取同样信息;
  • 流程问题:例如闪屏图每次下载,当次使用;

以及其它细节问题:

  • 执行无用老代码;
  • 执行开发阶段使用的代码;
  • 执行重复逻辑;
  • 调用三方SDK里或者Demo里的多余代码;

项目修改: 1. 数据库及IO操作都移到工作线程,并且设置线程优先级为THREAD_PRIORITY_BACKGROUND,这样工作线程最多能获取到10%的时间片,优先保证主线程执行。

2. 流程梳理,延后执行; 实际上,这一步对项目启动加速最有效果。通过流程梳理发现部分流程调用时机偏早、失误等,例如:

  • 更新等操作无需在首屏尚未展示就调用,造成资源竞争;
  • 调用了IOS为了规避审核而做的开关,造成网络请求密集;
  • 自有统计在Application的调用里创建数量固定为5的线程池,造成资源竞争,在上图traceview功能说明图中最后一行可以看到编号12执行5次,耗时排名前列;此处线程池的创建是必要但可以延后的。
  • 修改广告闪屏逻辑为下次生效。

3.其它优化;

  • 去掉无用但被执行的老代码;
  • 去掉开发阶段使用但线上被执行的代码;
  • 去掉重复逻辑执行代码;
  • 去掉调用三方SDK里或者Demo里的多余代码;
  • 信息缓存,常用信息只在第一次获取,之后从缓存中取;
  • 项目是多进程架构,只在主进程执行Application的onCreate();

通过以上三步及三方组件的优化:Application以及首屏Activity回调期间主线程就没有耗时、争抢资源等情况了。此外还涉及布局优化、内存优化等部分技术,因对于应用冷启动一般不是瓶颈点,这里不展开详谈,可根据实际项目实际处理。

六、对比效果:

通过ADB命令统计应用的启动时间:adb shell am start -W 首屏Activity。 同等条件下使用MX3及Nexus6P,启动5次,比较优化前与优化后的启动时间;

优化前: MX3

ThisTimeTotalTimeWaitTime
123722052214
128021812189
162225082513
148524342443
144224182429

Nexus6P

ThisTimeTotalTimeWaitTime
122918321868
126818491880
118417801812
126218451876
116417661807

优化后: MX3

ThisTimeTotalTimeWaitTime
86515161523
91115651573
81214061418
96215641574
92515661577

Nexus6P

ThisTimeTotalTimeWaitTime
60311921243
61410761115
65011201163
64211071139
62410841124

对比: MX3提升35%

ThisTime平均数TotalTime平均数WaitTime平均数
优化前14132349
优化后8951523

Nexus6P提升39%

ThisTime平均数TotalTime平均数WaitTime平均数
优化前12211814
优化后6261115
  • 命令含义:
    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进行分析,尽早发现瓶颈。

相关视频:

【2021最新版】Android studio安装教程+Android(安卓)零基础教程视频(适合Android 0基础,Android初学入门)_哔哩哔哩_bilibili

Android高级UI性能优化——FlowLayout流式布局项目实战长_哔哩哔哩_bilibili

Android高级UI性能优化——View的Measure原理应用与xml解析过程原理讲解_哔哩哔哩_bilibili

Android高级UI性能优化——LayoutInflater.inflate函数意义与参数说明_哔哩哔哩_bilibili

Android高级UI性能优化——ViewPager嵌套Fragment UI 模式性能优化_哔哩哔哩_bilibili

android性能优化之启动优化(代码片段)

Android性能优化之启动优化1.启动窗口优化Android系统在Activity的窗口尚未启动完成前,会先显示一个启动窗口(StartingWindow),等界面的第一帧渲染完成后再从启动窗口切换到真正的界面显示,启动窗口通常情况... 查看详情

android性能优化之启动优化(代码片段)

Android性能优化之启动优化1.启动窗口优化Android系统在Activity的窗口尚未启动完成前,会先显示一个启动窗口(StartingWindow),等界面的第一帧渲染完成后再从启动窗口切换到真正的界面显示,启动窗口通常情况... 查看详情

android性能优化之启动耗时测量(代码片段)

Android启动优化之启动耗时测量本文基于Android11.0源码分析,涉及如下文件frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.javaframeworks/base/services/core/java/com/android/server/wm/ActivityR 查看详情

android性能优化之启动耗时测量(代码片段)

Android启动优化之启动耗时测量本文基于Android11.0源码分析,涉及如下文件frameworks/base/services/core/java/com/android/server/wm/ActivityMetricsLogger.javaframeworks/base/services/core/java/com/android/server/wm/ActivityR 查看详情

android面试之必问性能优化(代码片段)

对于Android开发者来说,懂得基本的应用开发技能往往是不够,因为不管是工作还是面试,都需要开发者懂得大量的性能优化,这对提升应用的体验是非常重要的。对于Android开发来说,性能优化主要围绕如下方面展开:启动优化... 查看详情

❤️android性能优化之启动优化❤️(代码片段)

...赞、收藏、评论粉丝福利:公众号「帅次」一个分享Android体系技术·相关知识·面试题库·技术互助·干货·资讯·高薪职位·教程的地方。🔥背景        用户希望应用能够快速打开。启动时间过长的应用不能满足这个... 查看详情

抖音android性能优化系列:启动优化之理论和工具篇

Rhea指占用CPU进行计算所花费的时间绝对值,中断、挂起、休眠等行为是不会增加CPUTime的,所以因CPUTime开销占比高导致的不合理耗时点往往是逻辑本身复杂冗长需要消耗较多cpu时间片才能处理完。比较常见的高CPU占用是循环,比... 查看详情

android面试之必问性能优化(代码片段)

对于Android开发者来说,懂得基本的应用开发技能往往是不够,因为不管是工作还是面试,都需要开发者懂得大量的性能优化,这对提升应用的体验是非常重要的。对于Android开发来说,性能优化主要围绕如下方... 查看详情

android性能优化之splash页应该这样设计

目前SplashActivity的设计目前市场上的应用在启动时基本上都会先启动一个SplashActivity,作为一个欢迎界面,为什么这样设计呢?个人总结有三个优点:1、可以给用户更好的体验比如:可以由后台动态的改变欢迎... 查看详情

android性能优化之启动速度优化

前言文本主要会介绍三大块:1.简略介绍APP启动的完整流程,对整个流程有所了解,才知道在哪里可以进行优化。2.一些常用的APP启动优化的方案,主要分为三大块优化方向。3.一些不常见的APP启动优化的方案,... 查看详情

性能优化之java(android)代码优化

性能优化之Java(Android)代码优化本文为Android性能优化的第三篇——Java(Android)代码优化。主要介绍Java代码中性能优化方式及网络优化,包括缓存、异步、延迟、数据存储、算法、JNI、逻辑等优化方式。(时间仓促,后面还会... 查看详情

android性能优化之启动速度优化(代码片段)

前言:文本主要会介绍三大块:1.简略介绍APP启动的完整流程,对整个流程有所了解,才知道在哪里可以进行优化。2.一些常用的APP启动优化的方案,主要分为三大块优化方向。3.一些不常见的APP启动优化的方... 查看详情

性能优化之java(android)代码优化

本文为Android性能优化的第三篇——Java(Android)代码优化。主要介绍Java代码中性能优化方式及网络优化,包括缓存、异步、延迟、数据存储、算法、JNI、逻辑等优化方式。(时间仓促,后面还会继续完善^_*)目前性能优化专题... 查看详情

android性能优化总提纲

Android性能优化(1)–性能优化介绍Android性能优化(2)—启动优化–1(启动优化介绍+启动时间测量)Android性能优化(2)—启动优化—2(方法耗时获取与异步初始化)Android性能优化(3)–内存优化–(内存优化工具、内存管理机制... 查看详情

android性能优化之内存泄漏检测以及内存优化(下)(代码片段)

上篇博客我们写到了Android中内存泄漏的检测以及相关案例,这篇我们继续来分析一下Android内存优化的相关内容。  上篇:Android性能优化之内存泄漏检测以及内存优化(上)。  中篇:Android性能优化之内... 查看详情

android性能优化之疑难杂症解决方案,u-apm的性能监控分析(代码片段)

关于Android发展至今,在各项功能十分成熟的情况下,我们越来越重视App的性能优化,以及用户体验,这关乎一个线上应用的DAU持续增长的基础,以及用户口碑的问题,今天刘某人带大家来一起分析一下崩... 查看详情

android性能优化之内存泄漏检测以及内存优化(中)(代码片段)

上篇博客我们写到了Java/Android内存的分配以及相关GC的详细分析,这篇博客我们会继续分析Android中内存泄漏的检测以及相关案例,和Android的内存优化相关内容。  上篇:Android性能优化之内存泄漏检测以及内存优化&... 查看详情

android性能优化之加快应用启动速度

应用的启动启动方式通常来说,在安卓中应用的启动方式分为两种:冷启动和热启动。1、冷启动:当启动应用时,后台没有该应用的进程,这时系统会重新创建一个新的进程分配给该应用,这个启动方式... 查看详情