共计 5342 个字符,预计需要花费 14 分钟才能阅读完成。
一、前言
随着我的项目版本的迭代,App 的性能问题会逐步裸露进去,而好的用户体验与性能体现严密相干,从本篇文章开始,我将开启一个 Android 利用性能优化的专题,从实践到实战,从入门到深挖,手把手将性能优化实际到我的项目中,欢送继续关注!
那么第一篇文章我就从利用的启动优化开始,依据理论案例,打造闪电般的 App 启动速度。
二、初识启动减速
利用的启动分为冷启动、热启动、温启动,而启动最慢、挑战最大的就是冷启动:零碎和 App 自身都有更多的工作要从头开始!利用在冷启动之前,要执行三个工作:
- 加载启动 App;
- App 启动之后立刻展现出一个空白的 Window;
- 创立 App 的过程;
而这三个工作执行结束之后会马上执行以下工作:
- 创立 App 对象;
- 启动 Main Thread;
- 创立启动的 Activity 对象;
- 加载 View;
- 安排屏幕;
- 进行第一次绘制;
而一旦 App 过程实现了第一次绘制,零碎过程就会用 Main Activity 替换曾经展现的 Background Window,此时用户就能够应用 App 了。
[图片上传失败 …(image-df49a0-1640595066640)]
作为一般利用,App 过程的创立等环节咱们是无奈被动管制的,能够优化的也就是 Application、Activity 创立以及回调等过程。
同样,Google 也给出了 启动减速的方向:
- 利用提前展现进去的 Window,疾速展现进去一个界面,给用户疾速反馈的体验;
- 防止在启动时做密集惨重的初始化(Heavy app initialization);
- 定位问题:防止 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 同样也有考究,这个话题在下文详谈。
我的项目批改:
- 将友盟、Bugly、听云、GrowingIO、BlockCanary 等组件放在 WorkThread 中初始化;
- 提早地图定位、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
ThisTime | TotalTime | WaitTime |
---|---|---|
1237 | 2205 | 2214 |
1280 | 2181 | 2189 |
1622 | 2508 | 2513 |
1485 | 2434 | 2443 |
1442 | 2418 | 2429 |
Nexus6P
ThisTime | TotalTime | WaitTime |
---|---|---|
1229 | 1832 | 1868 |
1268 | 1849 | 1880 |
1184 | 1780 | 1812 |
1262 | 1845 | 1876 |
1164 | 1766 | 1807 |
优化后: MX3
ThisTime | TotalTime | WaitTime |
---|---|---|
865 | 1516 | 1523 |
911 | 1565 | 1573 |
812 | 1406 | 1418 |
962 | 1564 | 1574 |
925 | 1566 | 1577 |
Nexus6P
ThisTime | TotalTime | WaitTime |
---|---|---|
603 | 1192 | 1243 |
614 | 1076 | 1115 |
650 | 1120 | 1163 |
642 | 1107 | 1139 |
624 | 1084 | 1124 |
比照: MX3 晋升 35%
ThisTime 平均数 | TotalTime 平均数 | WaitTime 平均数 |
---|---|---|
优化前 | 1413 | 2349 |
优化后 | 895 | 1523 |
Nexus6P 晋升 39%
ThisTime 平均数 | TotalTime 平均数 | WaitTime 平均数 |
---|---|---|
优化前 | 1221 | 1814 |
优化后 | 626 | 1115 |
- 命令含意:
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