EN

Android性能优化后续

字数统计: 2.7k阅读时长: 10 min
2015/03/31
loading

前言

本文是一篇译文,原文Android Performance Case Study Follow-up的作者是大名鼎鼎的Romain Guy。本文讲述了Android性能优化的一些技巧、方法和工具。

技术复核(2026-10-13):保留 2015 年 Falcon Pro 3 / Moto X 2014 的历史案例,以下第一人称属于原作者。Tracer for OpenGL ES、Hierarchy Viewer 所在的 Android Device Monitor 已在 Android Studio 3.1 弃用、3.2 移除;旧工具步骤标为历史内容,文末补充 Android 12+ 的 Perfetto 分析入口。原站链接本次未能重新读取,技术修订依据文中列出的官方资料和版本化源码。

译文正文

两年前,我发表了一篇名为Android Performance Case Study 的文章,来帮助Android开发者了解需要使用什么工具和技术手段来确定、追踪和优化性能问题。

那篇文章以一个Twitter客户端 Falcon Pro为典范,其开发人员为 Joaquim Vergès. Joaquim人不错,他允许我在我的文章中使用它的程序作为例子,并且快速处理了我发现的所有问题。一切都OK,直到Joaquim 从头开始开发Falcon Pro 3,前不久在他准备发布它的新应用的时候,他联系了我,因为他有一个和滚动相关的性能问题需要我来帮助他,这一次我依然没有源代码可以参考。

Joaquim使用了所有的工具来找出问题所在,他发现Overdraw不是问题的原因,他觉得是 ViewPager(当时使用 Support Library 版本,现有 AndroidX 参考) 的用法导致了这个问题。他给我发来了下面的截图:

Falcon Pro

Joaquim使用了系统内置的GPU profiling工具来发现掉帧现象, 左边的截图是在没有ViewPager 的情况下滑动时间线,右边的截图是有ViewPager的情况下滑动(他使用的是2014年的Moto x来截的图),问题看起来很明显。

我最先想到的是查看 ViewPager 是否在不必要地反复更新硬件层:这个性能问题看起来像是在滑动过程中每帧都重绘了一次 layer。这里怀疑的是硬件层的更新成本,而不是“每帧使用硬件加速”本身有错。系统的 hardware layers updates debugging tool没有显示什么有用的信息。我反复使用HierarchyViewer 查看布局情况,令我满意的是ViewPager的表现很正确(相反,不太可能会出问题)

之后我打开了另一个强大却不常用的工具:Tracer for OpenGL ES。它能记录应用的 OpenGL ES 调用,并在开启 framebuffer 回读时展示绘制过程中的图像;这里分析的是当年的 OpenGL ES 渲染路径,不能把它理解成适用于所有现代渲染后端的 GPU 时间测量器。历史工具说明

历史调试步骤:Android 4.3 之后,绘制命令的重排和合并让 Tracer 更难按 View 分组。下面这个开关已在 AOSP android-4.4.4_r2 的 Properties.h 中确认,可在启动应用前关闭旧 HWUI 的绘制延迟处理,以便观察调用顺序:

1
2
adb shell getprop debug.hwui.disable_draw_defer
adb shell setprop debug.hwui.disable_draw_defer true

先记录原值,分析完恢复并重新启动应用;原值未设置时,可设为 false 恢复正常路径。这个开关会改变被分析的渲染行为,不能把关闭优化后的耗时当正式性能结果,也不能直接套到新版本 Android。

阅读旧 OpenGL Trace:点一个蓝色的绘制调用,Details 面板就会显示它执行后的 framebuffer。按顺序一个个点下去,就能看到一帧是怎么画出来的,我通常就是这样分析的。但数据上传、状态切换和等待也可能很贵,不能因为它们不是绘制命令就忽略其性能影响。调用的 Wall Time / Thread Time 也不等于纯 GPU 执行时间。

滑动一段时间Falcon Pro应用后,我仔细查看Gl Trace收集到的数据,我很惊奇地发现很多 SaveLayer/ComposeLayer 分组。它们表示保存到离屏层、再合成回去的绘制过程,不是名为 SaveLayer 的线程阻塞指令。

Paste_Image.png

这些分组说明绘制经过了临时离屏目标。Canvas.saveLayer() 会把后续绘制转到离屏目标,再在 restore() 时合成回来;它与通过 View.setLayerType(LAYER_TYPE_HARDWARE, ...) 保留的 View 硬件层不是同一个概念。旧的带 saveFlags 重载已在 API 26 弃用,API 21 起有不带该参数的重载。Canvas 参考

在本文讨论的旧硬件加速路径中,常见触发组合是:

  • View 有半透明的 alpha,即 0 < alpha < 1;
  • onSetAlpha(int) 返回 false,没有由 View 自己处理透明度;
  • 没有预先配置 View layer,getLayerType() 为 LAYER_TYPE_NONE;
  • hasOverlappingRendering() 为 true,绘制内容可能相互覆盖。

可对照 Android 4.4.4 的 View 实现。这些条件用来理解本例,不是断言所有 Android 版本都会走同一条临时层路径。

我和 Chet 在演讲中解释过为什么要 谨慎使用 alpha。离屏绘制可能增加渲染目标切换、存储和合成成本;对本例的 Moto X 2014,这成为继续调查的线索。但不能只按 Adreno、SGX 或 Nvidia 的厂商名字推断今天某块 GPU 的表现,实际成本仍取决于硬件、驱动、渲染后端和内容。

那么问题来了,是什么创建了这些临时的Layer呢?Tracer告诉我们了答案,如果你看了上面的 GL Trace 图,就能看到 SaveLayer 分组里的绘制调用画出了一个小圆圈(图被工具放大了)。我们来看一下应用截图:

Falcon Pro 3

你看到最上面的小圆圈了么?那是ViewPager的指示器,来显示当前的位置。Joaquim 使用了一个第三方库来绘制这些指示器,有趣的是这些库如何绘制指示器的:当前的Page用一个白色的圈指示,其他的页用类似灰色的圆圈来指示。我说类似灰色因为这个圆圈其实是半透明的白色圆圈。这个库使用 setAlpha()方法来给每个圆圈设置颜色。

有下面几种方法来解决这个问题,前提是保持需要的视觉效果:

1. 直接使用未选中状态的颜色

让 View 的 alpha 保持 1,用 Paint 的颜色或 alpha 绘制圆点,避免仅为这个圆点对整个 View 设置透明度。只有背景固定且配色一致时,不透明灰色才可能代替半透明白色;背景变化时,两者不是同一种效果。

2. 如实声明没有重叠绘制

hasOverlappingRendering() 从 API 16 提供。如果这个 View 只画一个圆点,没有会与它重叠的背景、前景等内容,可以这样重写:

1
2
3
4
@Override
public boolean hasOverlappingRendering() {
return false;
}

系统据此可以把 alpha 分配到各绘制操作,避免为保持整组透明度而额外合成。这个方法描述的是同一个 View 内部是否存在重叠绘制,不是在问它是否与旁边的 View 相交。若实际存在重叠却返回 false,交叠处可能出现错误的颜色或透明度。API 合同

3. 由自定义 View 处理 alpha

onSetAlpha(int alpha) 的参数已经是 0–255 的整数。对于只绘制圆点的自定义 View,可以直接把它传给 Paint;下面是方法片段,indicatorPaint 是已初始化、在 onDraw() 中绘制该圆点的同一个 Paint:

1
2
3
4
5
@Override
protected boolean onSetAlpha(int alpha) {
indicatorPaint.setAlpha(alpha);
return true;
}

返回 true 表示自己负责整套绘制的透明度,不能只处理一支 Paint 却漏掉其他内容,也不能随后用 setColor() 覆盖刚设置的 alpha。原译文的 paint.setAlpha((int) alpha * 255) 有误:若输入是 getAlpha() 那样的 0–1 浮点数,应先乘再转换,如 (int) (alpha * 255);若输入就是 onSetAlpha() 的参数,则不要再乘 255。

对这里的单圆点 View,第二种写法较直接;在 API 16 之前则需要另外处理颜色或自身绘制。我想 Joaquim 也许已经改用自己的指示器了。

我希望这篇文章能让大家清楚如何从看似无辜的和无害的操作中寻找可能会出现性能问题。所以请记住:不要仅仅做出假设,要实际去验证、测量。

2026 更新:今天如何排查同类问题

这个案例值得保留的是从异常帧追到具体绘制内容的过程。新项目不必恢复整套旧 SDK:

  1. 先找异常帧。Android 12 / API 31 起,可采集包含 FrameTimeline 的 Perfetto trace,把预期和实际帧时间与主线程、RenderThread、SurfaceFlinger 的工作对应起来。不要仅凭一个 saveLayer 标记就认定它是掉帧根因。
  2. 再检查绘制内容。Android Studio 3.1 起可用 Layout Inspector 替代 Hierarchy Viewer 查看层级和属性;它不是同一套布局计时结果。若需要逐条 GPU 命令分析,可查看 AGI:要求受支持的 Android 11+ 真机和驱动,且 OpenGL ES 帧分析会通过 ANGLE 转成 Vulkan,不能把它当原生 GLES 路径的无扰动重放。旧工具退出情况见 Device Monitor 说明。
  3. 只改已确认的问题,再比较。在相同设备、场景和构建条件下,比较视觉结果及帧表现。背景变化、选中状态切换和 alpha 动画都要保持正确,性能收益应由实际采集支持。

渲染实现也已经变化。例如 android-16.0.0_r1 的 RenderNodeDrawable.cpp 会根据有效 layer 类型和重叠属性选择不同路径;源码甚至保留了用于提示旧版本风险的 alpha caused saveLayer trace 标签。因此,看到这个名字不能直接推断实际分配了新离屏缓冲区,更不能把 2015 年的耗时结论搬到新系统。

附录

原文补充阅读:Android Tips: Best Practices for Using Alpha。这是历史链接,本次未能重新读取;当前接口语义以上述 Android 官方参考和版本化源码为准。

关于我 && 博客

下面是个人的介绍和相关的链接,期望与同行的各位多多交流,三人行,则必有我师!

  1. 博主个人介绍 :里面有个人的微信和微信群链接。
  2. 本博客内容导航 :个人博客内容的一个导航。
  3. 个人整理和搜集的优秀博客文章 - Android 性能优化必知必会 :欢迎大家自荐和推荐 (微信私聊即可)
  4. Android性能优化知识星球:付费社群已于 2026 年停止运营,不再招收新成员;此处保留历史介绍。

一个人可以走的更快 , 一群人可以走的更远

微信扫一扫

CATALOG
  1. 1. 前言
  2. 2. 译文正文
    1. 2.1. 1. 直接使用未选中状态的颜色
    2. 2.2. 2. 如实声明没有重叠绘制
    3. 2.3. 3. 由自定义 View 处理 alpha
  3. 3. 2026 更新:今天如何排查同类问题
  4. 4. 附录
  5. 5. 关于我 && 博客