走马观花看Android性能优化工具 | 青训营笔记

393 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的第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层:

  1. 应用层
  2. 框架层
  3. 虚拟机 核心库
  4. 操作系统内核
  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背景
  • 绘制层级优化

异步渲染,主线程只做交互

  • SurfaceView:采用独立的线程进行绘制和渲染,生命周期需要自己绘制
  • Jetpack Compose:基于组合优于继承的思想,重新设计一套解耦的UI框架
  • Litho:复杂UI下的高性能渲染框架,
  • 扁平化:采用Yoga完成组件布局measure和layout,实现布局的扁平化
  • 组件化:Litho使用Drawable作为Node绘制单元,实现了布局的细粒度复用和异步计算布局的能力
  • 缺点:不支持现有逻辑,需要重新实现