Android性能优化-过度渲染

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/wangjiang_qianmo/article/details/84252422

过度渲染

去除过度渲染可以从下面渲染阶段的几方面入手:

  • 交换缓冲区阶段,表示 CPU 等待 GPU 完成其工作的时间。 如果此竖条升高,则表示应用在 GPU 上执行太多工作。
  • 命令问题阶段, 表示 Android 的 2D 渲染器向 OpenGL 发起绘制和重新绘制显示列表的命令所花的时间。 此竖条的高度与它执行每个显示列表所花的时间的总和成正比—显示列表越多,红色条就越高。
  • 同步和上传阶段,表示将位图信息上传到 GPU 所花的时间。 大区段表示应用花费大量的时间加载大量图形。
  • 绘制阶段,表示用于创建和更新视图显示列表的时间。 如果竖条的此部分很高,则表明这里可能有许多自定义视图绘制,或 onDraw 函数执行的工作很多。
  • 测量/布局阶段,表示在视图层次结构中的 onLayout 和 onMeasure 回调上所花的时间。 大区段表示此视图层次结构正在花很长时间进行处理。
  • 动画阶段,表示评估运行该帧的所有动画程序所花的时间。 如果此区段很大,则表示您的应用可能在使用性能欠佳的自定义动画程序,或因更新属性而导致一些意料之外的工作。
  • 输入处理阶段,表示应用执行输入 Event 回调中的代码所花的时间。 如果此区段很大,则表示此应用花太多时间处理用户输入。 考虑将此处理任务分流到另一个线程。
  • 其他时间/VSync 延迟阶段,表示应用执行两个连续帧之间的操作所花的时间。 它可能表示界面线程中进行的处理太多,而这些处理任务本可以分流到其他线程。

具体如何分析和优化过度渲染,可以查看我之前的博客关于过度绘制和渲染的介绍


补充

GAPID

GAPID是一个开发工具,用于记录和检查应用程序对图形驱动程序的调用。

在这里插入图片描述

一旦捕获了目标应用程序,GAPID允许您断开与目标的连接,并检查应用程序执行的所有图形命令。

GAPID能够重放命令流,通过逐步遍历每个命令并检查流中任何点的驱动程序状态,使框架组合可视化。Replay还支持修改,允许您调整命令参数和着色器源,以便立即查看这会对帧产生什么影响。

GAPID还可以可视化应用程序所使用的纹理、着色器和绘制调用几何结构

Capturing a trace

GAPID支持从Android设备和Windows /Linux桌面机器上捕获。在Android设备上,GAPID支持跟踪由纯Java、本地或混合应用程序生成的所有OpenGL ES和Vulkan调用。在Windows /Linux桌面机上,GAPID支持跟踪Vulkan调用。

依赖和前提条件

Android:

  • 运行在5.0设备以上
  • 要么是可调试的应用程序,要么是运行“rooted”用户调试生成的设备
  • 电脑安装了Android SDK
  • 通过USB连接的Android硬件设备
  • 该设备必须启用USB调试,并且主机必须被授权进行调试
捕捉

单击欢迎屏幕中的Capture Trace文本,或者单击File → Capture Trace工具栏项打开跟踪对话框。
在这里插入图片描述

  1. 选择要跟踪的设备。
  2. 选择要跟踪的图形API。
  3. 选择Android Activity或浏览要跟踪的应用程序。
  4. 添加程序所需的任何命令行参数。
  5. 为您的程序选择工作目录,只适用于在Windows /Linux机器上进行跟踪。
  6. 设置用于跟踪程序的环境变量,仅适用于在Windows /Linux机器上进行跟踪。
  7. 如果希望在N帧后自动停止跟踪,则使用非零数字停止。
  8. 如果希望在启动应用程序后立即开始跟踪,请启用“从开始”选项。如果未设置此选项,则在跟踪对话框中,必须按“开始”来开始捕获。
  9. Disable Buffering使跟踪设备上的捕获数据的缓冲无效,这将减慢跟踪过程。但是,如果发生碰撞,将提供更多的最新数据。
  10. 如果您想在跟踪之前擦除包缓存,请启用清除包缓存选项。
  11. Hide Unknown Extensions 在跟踪Vulkan调用时隐藏GAPID不支持应用程序的Vulkan扩展。对于GELS调用,它没有做任何事情。在跟踪OpenGL ES调用时,GAPID总是隐藏未知的扩展名。
  12. 如果跟踪OpenGL ES应用程序,您可能希望启用禁用的预编译着色器选项。此选项不篡改对OpenGL ES的预编译着色器的驱动程序支持,通常迫使应用程序使用glShaderSource()。GAPID当前无法重放在跟踪OpenGL ES时使用预编译着色器的捕获。此选项在跟踪Vulkan调用时无效。
  13. 选择输出目录。
  14. 选择输出文件名。

单击“OK”开始跟踪。

例子:

第一步:下载GAPID并安装。
第二步:打开GAPID并按照上面的捕捉步骤操作。

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

从上面图中可以看到应用程序每一帧的渲染,以及每一帧从CPU上传到GPU的图片纹理。如果发现有上传重复的图片或者渲染无用的渐变、阴影,可以针对这些进行优化,因为这些都会增加每一帧的渲染时间。


systrace

systrace命令允许您在系统级收集并检查在设备上运行的所有进程的实时信息。它将来自Android内核的数据(如CPU调度程序、磁盘活动和应用程序线程)组合起来生成HTML报告,类似于图1所示。
在这里插入图片描述
图1. 一个systrace HTML报表例子,显示了与应用程序交互的5秒。报告强调了systrace认为可能没有被正确渲染的帧。

该报告提供了Android设备在给定时间内的系统进程的总体图。它还检查捕获的跟踪信息,以突出它观察到的问题,如显示运动或动画时的UI jank,并提供关于如何修复它们的建议。但是,systrace没有收集应用程序过程中有关代码执行的信息。有关应用程序正在执行哪些方法和使用多少CPU资源的详细信息,请使用Android Studio CPU profiler。您还可以生成跟踪日志,并使用CPU profiler导入和检查它们。

语法


要为app生成HTML报告,您需要使用以下语法从命令行运行systrace:

$ python systrace.py [options] [categories]

systrace.py 位于 android-sdk/platform-tools/systrace/ 目录下。在执行命令前,确保你已经安装了python。

要查看已连接设备支持的类别列表,请运行以下命令:

$ python systrace.py --list-categories

在这里插入图片描述

如果未指定任何类别或选项,systrace将生成包含所有可用类别的报告并使用默认设置。 可用的类别取决于您使用的连接设备。

全局选项
全局选项 描述
-h | --help 显示帮助信息。
-l | --list-categories 列出所连接设备可用的跟踪类别。
命令和命令选项
命令和选项 描述
-o file 将HTML跟踪报告写入指定的文件。 如果未指定此选项,systrace会将报表保存到与systrace.py相同的目录中,并将其命名为trace.html。
-t N | --time=N 跟踪设备活动N秒。 如果未指定此选项,则systrace会提示您通过从命令行按Enter键来结束跟踪。
-b N | --buf-size=N 使用N千字节的跟踪缓冲区大小。 此选项允许您限制跟踪期间收集的数据的总大小。
-k functions | --ktrace=functions 跟踪在逗号分隔列表中指定的特定内核函数的活动。
-a app-name | --app=app-name 启用应用程序的跟踪,指定为以逗号分隔的进程名称列表。 应用程序必须包含Trace类的跟踪检测调用。 您应该在配置应用程序时指定此选项,许多库(例如RecyclerView)包括跟踪检测调用,这些调用在您启用应用程序级别跟踪时提供有用信息。 有关更多信息,请转到有关如何检测应用程序代码的部分。
--from-file=file-path 从文件创建交互式HTML报告,例如包含原始跟踪数据的TXT文件,而不是运行实时跟踪。
-e device-serial--serial=device-serial 在特定连接设备上进行跟踪,由设备序列号标识。
categories 包括您指定的系统进程的跟踪信息,例如用于呈现图形的系统进程的gfx。 您可以使用-l命令运行systrace,以查看连接设备可用的服务列表。

调查UI性能问题


systrace对于检查应用程序的UI性能特别有用,因为它可以分析您的代码和帧速率,以识别问题区域并提出可能的解决方案建议。 首先,按以下步骤操作:

  1. 在连接的设备上运行您的应用。
  2. 使用以下命令运行systrace:
$ python systrace.py -t 10 [other-options] [categories]
  1. 在systrace继续运行时与您的应用交互。
  2. 在您定义的时间限制结束后,systrace会生成HTML报告。
  3. 使用Web浏览器打开HTML报告。

通过与此报告交互,您可以在记录期间检查设备CPU使用情况。

以下部分介绍了如何检查报表中的信息以查找和修复UI性能问题。

检查帧率和警报

如图2所示,报表列出呈现UI帧的每个进程,并指示沿着时间线呈现的每个帧。在16.6毫秒内渲染的帧需要保持每秒60帧的稳定性,用绿色帧圆表示。渲染时间大于16.6毫秒的帧用黄色或红色帧圆表示。

在这里插入图片描述
图2.放大长时间运行帧后的Systrace显示。

注意:在运行Android 5.0(API级别21)或更高版本的设备上,渲染帧的工作在UI线程和RenderThread之间分配。 在以前的版本中,创建帧的所有工作都在UI线程上完成。

单击框架圆圈会突出显示它,并提供有关系统完成该帧所做工作的其他信息,包括警报。 它还会向您显示系统在渲染该帧时执行的方法,因此您可以调查这些方法以获取UI jank的原因。

在这里插入图片描述
图3.选择有问题的帧,跟踪报告下方会出现一个警报,用于识别问题。

在选择慢帧之后,您可能会在报告的底部窗格中看到警报。 图3中显示的警报调用框架的主要问题是在ListView回收和重新绑定中花费了太多时间。 跟踪中的相关事件有链接,可以更详细地说明系统在此期间所执行的操作

要查看工具在跟踪中发现的每个警报,以及设备触发每个警报的次数,请单击窗口右侧的“Alert”选项卡,如图4所示。Alert面板帮助您了解跟踪中出现哪些问题,以及它们对jank有多频繁。 将面板视为要修复的错误列表。 通常,一个区域中的微小变化或改进可以从应用程序中消除整个类别的警报。

在这里插入图片描述

图4.单击右侧的Alert按钮显示警告选项卡。

如果你在UI线程上看到太多的工作,你需要找出哪些方法消耗了太多的CPU时间。 一种方法是将跟踪标记(请参阅仪器您的应用程序代码)添加到您认为导致这些瓶颈的方法中,以查看这些函数调用是否出现在systrace中。 如果您不确定哪些方法可能导致UI线程出现瓶颈,请使用Android Studio CPU分析器。 您可以使用CPU Profiler生成跟踪日志并导入和检查它们。

HTML报告键盘快捷键

下表列出了查看systrace HTML报告时可用的键盘快捷键。

Key 描述
W 放大跟踪时间线。
S 缩小跟踪时间线。
A 在追踪时间线上左移。
D 在追踪时间线上右移。/td>
E 将跟踪时间轴置于当前鼠标位置的中心。/td>
G 在当前所选任务的开头显示网格。
Shift + G 在当前所选任务的末尾显示网格。
Right Arrow 选择当前所选时间轴上的下一个事件。
Left Arrow 在当前选定的时间轴上选择上一个事件。

检测应用代码


因为systrace只在系统级别显示关于进程的信息,所以很难在HTML报告中知道应用程序在给定时间执行了哪些方法。在Android 4.3(API级别18)和更高级别中,可以使用代码中的跟踪类来标记HTML报表中的执行事件。您不需要使用systrace来记录代码来记录跟踪,但是这样做可以帮助您了解应用程序代码的哪些部分可能对挂起线程或UI jank有所贡献。这种方法与使用Debug类不同——Trace类只是向systrace报告添加标记,而Debug类通过生成.trace文件帮助您检查详细的应用程序CPU使用情况。

要生成包含已检测跟踪事件的systrace HTML报告,您需要使用-a或–app命令行选项运行systrace,并指定应用程序的包名称。

下面的示例代码演示如何使用Trace类来标记方法的执行,该方法包括两个嵌套的代码块:

public class MyAdapter extends RecyclerView.Adapter<MyViewHolder> {
    ...
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        Trace.beginSection(&quot;MyAdapter.onCreateViewHolder&quot;);
        MyViewHolder myViewHolder;
        try {
            myViewHolder = MyViewHolder.newInstance(parent);
        } finally {
            Trace.endSection();
        }
        return myViewHolder;
    }

   @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        Trace.beginSection(&quot;MyAdapter.onBindViewHolder&quot;);
        try {
            try {
                Trace.beginSection(&quot;MyAdapter.queryDatabase&quot;);
                RowItem rowItem = queryDatabase(position);
                mDataset.add(rowItem);
            } finally {
                Trace.endSection();
            }
            holder.bind(mDataset.get(position));
        } finally {
            Trace.endSection();
        }
    }
...
}

注意:beginSection()和endSection()方法需要结对出现,并且在同一个线程中运行。

猜你喜欢

转载自blog.csdn.net/wangjiang_qianmo/article/details/84252422