- 可以直观看到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 | 2357 |
| 优化后 | 895 | 1523 | 1533 |
Nexus6P提升39%
| 对比 | ThisTime平均数 | TotalTime平均数 | WaitTime平均数 |
|---|---|---|---|
| 优化前 | 1221 | 1814 | 1848 |
| 优化后 | 626 | 1115 | 1156 |
- 命令含义:
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进行分析,尽早发现瓶颈。
结尾
好啦,文章写到这里就结束了,如果你觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。

希望读到这的您能转发分享和关注一下我,以后还会更新技术干货,谢谢您的支持!
转发+点赞+关注,第一时间获取最新知识点
Android架构师之路很漫长,一起共勉吧!
以下墙裂推荐阅读!!!
面试复习路线,梳理知识,提升储备
自己的知识准备得怎么样,这直接决定了你能否顺利通过一面和二面,所以在面试前来一个知识梳理,看需不需要提升自己的知识储备是很有必要的。
关于知识梳理,这里再分享一下我面试这段时间的复习路线:(以下体系的复习资料是我从各路大佬收集整理好的)
- 架构师筑基必备技能
- Android高级UI与FrameWork源码
- 360°全方面性能调优
- 解读开源框架设计思想
- NDK模块开发
- 微信小程序
- Hybrid 开发与Flutter

知识梳理完之后,就需要进行查漏补缺,所以针对这些知识点,我手头上也准备了不少的电子书和笔记,这些笔记将各个知识点进行了完美的总结:

《960全网最全Android开发笔记》

《379页Android开发面试宝典》
历时半年,我们整理了这份市面上最全面的安卓面试题解析大全
包含了腾讯、百度、小米、阿里、乐视、美团、58、猎豹、360、新浪、搜狐等一线互联网公司面试被问到的题目。熟悉本文中列出的知识点会大大增加通过前两轮技术面试的几率。
如何使用它?
1.可以通过目录索引直接翻看需要的知识点,查漏补缺。
2.五角星数表示面试问到的频率,代表重要推荐指数

《507页Android开发相关源码解析》
只要是程序员,不管是Java还是Android,如果不去阅读源码,只看API文档,那就只是停留于皮毛,这对我们知识体系的建立和完备以及实战技术的提升都是不利的。
真正最能锻炼能力的便是直接去阅读源码,不仅限于阅读各大系统源码,还包括各种优秀的开源库。
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
本文探讨了在Android应用冷启动时,如何通过分析主线程耗时、合理使用工作线程、调整线程工具如ThreadPoolExecutor和避免滥用异步,来优化启动性能。作者给出了具体的优化步骤和案例,如数据库和IO操作的线程迁移、网络请求的合并和组件初始化策略等。

996

被折叠的 条评论
为什么被折叠?



