这是我参与「第四届青训营 」笔记创作活动的第5天
走马观花看Android性能优化工具 | 青训营笔记
为什么要做性能优化
- 性能优化带来体验的改善,进而帮助业务指标的提升
- 硬件性能提升速度变缓
- ARM平台受益于架构和工艺的演进,近几年趋势比x86好
- 多核带来的提升取决于可以真正并行执行的部分
- 未来会有新的材料和工艺来驱动芯片性能的进一步提升
- 移动处理器受到电池技术的限制
- 软件的性能优化仍可持续带来提升
性能优化是什么
目标:快、稳、省
- 快:启动快、加载快、交互快、UI流畅
- 稳:不闪退、不ANR、不异常
- 省:减少包体积、减小安装后大小、节省运行内存、节省缓存
流畅性优化
快-更极致的效应与流畅的体验
Android的线程结构{
Main Thread(UI线程){
System Events,
Input Events,
Application,
Services,
Alarm,
UI Drawing
}
}
界面是不断被系统刷新的,每16ms刷新一次,即60帧。VSync。
卡顿的原因
- 主线程在进行重要操作,输入事件无法响应,卡住
- 输入事件已响应,但事件处理耗时较长,滑一下卡一下
- 同一时间内有多个任务要处理,没有输入
解决方案:
- 将耗时操作移动到其他线程
- 算法优化
人眼能感知到不卡顿的最低帧数是25帧。
如果没有VSync信号,可能导致画面撕裂——绘制一半被别的画面覆盖。原因在于屏幕只是IO设备,没有控制功能,需要VSync来控制数据交换和渲染。
资源优化
省-最小负载带来最大收益
资源:Android手机的软件和硬件资源,通俗意义上应用依赖的移动终端的有限资源和系统设置的数值,即功耗、存储、流量、系统参数、CPU、内存、发热、流量、后台冻结等。
有趣课题:在未优化的硬件上让应用有更好的效果。让有限的资源发挥更好的效果。需要了解硬件。
内存不足,会不断地向系统申请任务,变得很卡。
"资源优化":{
"端侧资源": [
"功耗",
"内存",
"存储",
"CPU",
"GPU",
"网络",
"音量",
"亮度",
"..."
],
"服务侧资源":[
"CDN宽带",
"API流量"
]
}
ANR: Application Note Responding
稳定性优化
稳-稳定的实现,减少不必要的打断
"稳定性优化":[
"崩溃",
{"超时":[
"UI JanK / 卡顿掉帧",
"ANR / 应用无响应"
]}
],
系统级优化
拓展-底层booster
可以从编译器和工具优化,也可以从系统的不同层次优化。
系统分5层:
- 应用层
- 框架层
- 虚拟机 核心库
- 操作系统内核
- 硬件
最佳工具选型
性能监控价值
- 监控和优化相生相伴
- 监控有攻也有防
- 攻是为了发现现有问题,知道优化方向
- 防是为了发现劣化问题。及时止损
- 线上监控发现问题并聚合排序,线下监控作为线上辅助,并发版前置发现问题
GPU呈现模式
开发者模式 -> GPU呈现模式
原理:系统通过记录每一帧的相关数据,然后通过图形的形式呈现
优点:无需二次开发,简单易用
缺点:并不完全准确,且无法明确指出造成卡顿问题的具体原因
Layertool
原理:遍历ViewTree信息,输出View层级关系
优点:清楚明了,可以宏观感知ViewTree现状,也可以定制,帮助分析overdraw
缺点:还不能够清楚明确的分析出UI的性能瓶颈
CPU Profiler
原理:基于JVMTI
优点:完整的方法调用栈输出、支持Java、C、C++方法耗时检测、上手简单
缺点:性能损耗太大
TraceView
- Instrument
- 虚拟监听函数入口回调,Enter/Exit/Unwind
- 耗时点:读时间、写数据到buffer、加锁等指令
- Sample
- 通过定时抓取多次堆栈diff,近似确定函数的进入和退出时间
- 耗时点:堆栈diff、同Instrument
- 间隔抓取堆栈的时间越长,性能损耗越少,而越会导致端函数检测不到
Systrace
- ftrace: debugfs采集和读取trace数据,记录 trace events
- atrace: 用户侧的 trace 跟踪,聚合所有的 trace event
- 系统级的 Trace 数据:锁监控 等
btrace(rhea)
rhea-systrace: 全函数插桩,自动生成Trace代码,对层数做限制,性能损耗50%
rhea-mtrace: 全函数插桩,抛弃systrace,自己统计函数耗时,最后数据展现同systrace
rhea-atrace: 优化systrace性能,聚合更多性能数据:类加载、Lock、IO等
Battery Historian
查功耗问题
如何优化
现状分析
耗时归因:
- CPU Time:循环,反射,序列化/反序列化,类解析
- IO Wait:IO操作,等待IO返回结果
- IPC:Binder调用耗时
- Lock Wait:主线程是等锁状态,等待其他线程或者自己超时唤醒
- CPU Schedule:主线程是可执行状态,但是获取不到CPU时间片
运行环境归因:
- 根据耗时归因归类
- 根据运行所在线程环境采用不同的策略
主线程的容忍度远远低于后台线程的,主线程只有少于16ms的时间。要求不阻塞前台,减少Lock Wait。
- 主线程优化
- 运行时优化
- 后台线程优化
抖音启动耗时归因
- 冷启
- 创建进程
- ContentProvider初始化
- Application#Create
- 目标是3秒内启动
- 温启
- 创建Activity
- inflate view hierarchy
- 目标是1秒内启动
- 热启
- Activity#onStart()
抖音冷启耗时分布:
- CPU调度 4.028%
- Sleep 11.782%
- IO Wait 39.174%
- CPU Running 45.015%
渲染分析
并行可能有锁的竞争
优化策略
UI构建解决方案:
- 耗时成因:xml IO、class反射、创建View、Asset资源大锁
- 官方解决方案:AsyncLayoutInflater
- 抖音解决方案:
- AndInflater:编译时解析xml
- LegoInflate:高优先级的启动预加载方案
- AsyncInflater:随时随地预加载,不予具体逻辑绑定,生命周期存活,自定义清理周期
数据请求+解析
- GSON解析,复用Gson对象,而不是每次解析都创建新的Gson对象
- 用protobuf取代json
渲染耗时优化
- 移除不必要的背景图
- 修改不合理布局
- 写高效合理的布局
- 移除默认的Window背景
- 绘制层级优化
异步渲染,主线程只做交互