Android学习

Android 之如何优化 UI 渲染(上)

2019-11-12  本文已影响0人  godliness

UI 优化系列专题,来聊一聊 Android 渲染相关知识,主要涉及 UI 渲染背景知识如何优化 UI 渲染两部分内容。


UI 优化系列专题

View 绘制流程之 setContentView() 到底做了什么?
View 绘制流程之 DecorView 添加至窗口的过程
深入 Activity 三部曲(3)View 绘制流程
Android 之 LayoutInflater 全面解析
关于渲染,你需要了解什么?
Android 之 Choreographer 详细分析

Android 之如何优化 UI 渲染(上)
Android 之如何优化 UI 渲染(下)


从产品和设计师的角度,他们自然希望应用可以使用丰富的图形元素、更炫酷的动画来实现流畅的用户体验。但是 Android 系统很有可能无法及时完成这些复杂界面的渲染操作,这个时候就会出现掉帧。也正因如此,我们才需要做 UI 优化。UI 优化要解决的核心问题是,由渲染性能本身造成用户感知的卡顿,可以把它理解为卡顿优化的一个子集。

工欲善其事,必先利其器,在做 UI 优化之前,我们必须先要找到由渲染性能引起卡顿的问题点,有哪些工具可以帮助我们分析和测量呢?今天我们先来聊一聊 UI 渲染的测量方法。

布局分析

在 Android 中,绝大多数视图元素都是通过 xml 布局完成的,接下来我们就从布局优化开始逐步的排查和分析。

1. Hierarchy Viewer

说到布局优化,不得不提 Hierarchy Viewer 这款利器,它是 Android Device Monitor 中内置的一种布局性能分析工具,它通过栅格化获取当前布局界面的显示基元,并将其转换为屏幕像素的过程。

Hierarchy Viewer 可以测量每个视图节点相对于其他同级视图的性能,因此分析结果中总是会有红色节点,红色节点并不一定意味着该节点的布局性能表现不佳。不过它却是当前视图层级中表现最差的。

所选节点的每个子级都有三个圆点,可以是绿色、黄色或红色

这些圆点大致对应绘制流程的测量、布局和绘制阶段。圆点的颜色表示在当前层级内相对于其他节点的性能对比。

需要注意,如果应用的运行速度出乎意料的慢,则红色节点可能是有问题的;在同一个层级内,总有一个最慢的视图节点,但是我们只需要确保它是按照我们“预期”的结果即可。那该如何正确理解是符合预期的红色圆点呢?

虽然 Hierarchy Viewer 并不能真实反映设备上的实际渲染性能,但是我们可以通过多次分析以了解平均测量结果。总的来看, 它还是能够帮助我们检查布局结构中每个视图模块的渲染性能,以及查找出冗余或导致性能瓶颈的视图节点。

Layout Inspector

不过,在 Android Studio 3.1 及以后, Hierarchy Viewer 已经被弃用,此时 Android 推荐使用 Layout Inspector 来检查应用的视图层次结构。

使用 Layout Inspector 可以将应用布局与设计模型进行比较、显示应用的放大视图,并在运行时检查其布局细节。

整体来看,Layout Inspector 并没有太多亮点功能,相反它还取消了 Hierarchy Viewer 中有关布局性能的分析信息。Hierarchy Viewer 接入真机调试也非常容易,具体你可以参考这里

2. Use Lint

在布局文件中运行 Lint 工具,以搜索视图结构中可能潜在的问题,将始终是一个好的习惯!Lint 取代了早期 Layoutopt 工具,并且已经默认被集成到 Android Studio。

无论何时编译项目 Lint 都会自动运行,检查 Android 源文件是否存在潜在的错误,并提供修改建议以及可以直接跳转到问题代码进行审查。那 Lint 可以帮助我们检查哪些布局问题呢?

TextView tv = (TextView) findViewById( R.id.textView );
tv.setCompoundDrawablesWithIntrinsicBounds( 0, R.drawable.ic_launcher, 0, 0 );

下图展示了 Lint 工具如何检查应用源文件,源文件包括:Java、Kotlin 和 XML 文件、图标以及 ProGuard 配置文件等。有关 Lint 的更多配置你可以参考《使用 Lint 检查改进您的代码


渲染测量

1. Show GPU Overdraw

在 Android 4.2,系统增加了检测过渡绘制(Overdraw)的工具,通过对应用界面进行颜色编码来帮助我们识别过渡绘制。具体可以参考《检查 GPU 渲染速度和绘制过渡》。

当应用界面在同一帧内多次绘制同一个像素时,便会发生过渡绘制。这种可视化会工具可以显示出应用界面不必要的渲染工作;因为,这样的绘制任务往往对用户是不可见的。因此,我们应该尽可能避免过渡绘制的场景。

过渡绘制颜色等级划分为 5 个阶段:

  1. 真彩色:没有发生过渡绘制;每个像素点仅绘制 1 次;

  2. 蓝色:过渡绘制 1 次,这部分像素在屏幕上绘制了 2 次;

  3. 绿色:过渡绘制 2 次,这部分像素在屏幕上绘制了3 次;

  4. 粉色:过渡绘制 3 次,这部分像素在屏幕上绘制了 4 次;

  5. 红色:过渡绘制 4 次或更多次,这部分像素在屏幕上绘制了 5 次以上。

注意,有些过渡绘制可能是不可避免的。在实际开发过程中,我们应尽可能保证更多的原色或蓝色区域,极少部分的绿色或粉色,最好不要出现红色。

2. Profile GPU Rendering

我们还可以开启 Profile GPU Rendering 检查界面的渲染性能。该工具以滚动柱状图的形式,直观地展示了渲染界面每帧所花费的时间(以每帧 16ms 的速度作为对比基准)。

在 Android 6.0(API Level 23)之后,会输出下面的计算和绘制每个阶段的耗时:

在 4.0(API Level 14)和 5.0(API Level 21)之间的 Android 版本具有蓝色、紫色、红色和橙色区段。低于 4.0 的 Android 版本只有蓝色、红色和橙色区段。

如果我们把上面的步骤转化为线程模型,可以得到下面的流水线模型。CPU 将数据同步(sync)给 GPU 之后,一般不会阻塞等待 GPU 渲染完毕,而是通知结束后就返回。而 RenderThread 承担了比较多的绘制工作,分担了主线程很多压力,提高了 UI 线程的响应速度。

通过上面的一些分析和测量工具,我们可以初步判断应用 UI 渲染的性能是否达标,例如经常出现的掉帧、掉帧主要发生在哪一个阶段、是否存在 Overdraw 等。


数据测量

虽然这些图形化界面工具非常好用,但是它们难以提供准确的测量数据,那有哪些测量方法可以更精确的实现渲染测量呢?

1. Systrace

在 Android 4.1,系统还新增了 Systrace 性能分析工具。Systrace 默认只能监控系统特定调用的耗时情况,例如跟踪系统的 I/O 操作、CPU 负载、Surface 渲染、GC 等事件。而且性能开销非常低。

Systrace 利用了 Linux 的 ftrace 调试工具,相当于在系统各个关键位置都添加了一些性能探针,也就是在代码里加了一些性能监控的埋点。Android 在 ftrace 的基础上封装了 atrace,并增加了更多特有的探针,例如 Graphics、Activity Manager、Dalvik VM、System Server 等。

Systrace 会生成一份包含多个部分的 HTML 文件,包括每个进程渲染界面沿时间轴所指明的渲染帧。具体该如何解读这份报告你可以参考 Systrace 报告


拿起笔,划重点了

由于系统预留了 Trace.beginSection 接口来监听应用程序的调用耗时,我们可以在 Systrace 上增加应用程序的耗时分析:通过编译时给每个函数插桩的方式来实现,也就是在重要函数的入口和出口分别增加 Trace.beginSection 和 Trace.endSection。当然出于性能考虑,我们需要过滤大部分指令数比较少的函数,这样就实现了在 Systrace 基础上增加应用程序耗时的监控。具体你可以参考为 Systrace 定义自定义事件

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

Tracer for OpenGL ES 也是 Android 4.1 新增加的工具,它可以逐帧、逐函数的记录 App 用 OpenGL ES 的绘制过程。它提供了每个 OpenGL 函数调用的消耗时间,所以很多时候用来做性能分析。但因为其强大的记录功能,在分析渲染问题时,当 TraceView、Systrace 都显得棘手,还找不到问题所在时,此时这个工具就会派上用场了。

Graphics API Debugger

不过,正如前面文章中介绍 Android 渲染框架的演进非常快,在 Android Studio 3.1 之后,Android 推荐使用 Graphics API Debugger (GAPID)来替代 Tracer for OpenGL ES 工具。GAPID 可以说是它的升级版本,不仅支持跨平台,而且功能更加强大,支持 Vulkan 和回放。

通过上面的几个工具,我们可以进一步判断、分析应用 UI 渲染性能数据,并且能够找出潜在的渲染瓶颈点。


自动化测试场景

虽然上面这些工具已经非常强大,不过线下的测试无论如何也难以复现线上大数据用户场景,此时我们需要一套能够在产品上线后的自动化测量工具,那有哪些测量方法可以满足我们的需求呢?

1. gfxinfo

gfx info 可以输出包含各阶段的动画以及帧相关的性能分析,具体命令如下:

adb shell dumpsys gfxinfo packageName

除了渲染的性能之外,gfxinfo 还可以拿到渲染相关的内存和 View Hierarchy 信息。

View Hierarchy:

com.xxx.android.xxx/com.weex.app.DebugActivity/android.view.ViewRootImpl@f80e215 
14 views, 11.05kB of display lists

com.xxx.android.xxx/com.weex.app.WeexActivity/android.view.ViewRootImpl@d92a
84 views, 96.78kB of display lists

Total ViewRootImol:2
Total Views:       98
Total DisplayList: 107.82 kB

在 Android 6.0 之后,gfxinfo 命令新增了 framestats 参数,可以拿到最近 120 帧每个绘制阶段的耗时信息。

adb shell dumpsys gfxinfo packageName framestats

通过这个命令我们可以实现自动化统计应用的帧率,更进一步还可以实现自定义的 “Profile GPU Rendering” 工具,在出现掉帧的时候,自动统计分析是哪个阶段的耗时增长最快,同时给出相应建议

2. SurfaceFlinger

除了耗时,我们还比较关心渲染使用的内存。在前面文章我们有讲过,Android 4.1 以后每个 Surface 都会有三个 Graphic Buffer,那如何查看 Graphic Buffer 占用的内存,系统是怎么样管理这部分的内存的呢?

你可以通过下面的命令拿到 SurfaceFlinger 相关的信息:

adb shell dumpsys SurfaceFlinger

以我的测试项目为例,应用使用了三个 Graphic Buffer 缓冲区,当前用在显示的第 0 个 Graphic Buffer,大小是 1080 * 1920。这样我们可以更好地理解三缓冲机制,而且可以看到这三个 Graphic Buffer 的确在交替使用。

Layer 0x7b5dfb7000 (com.xxx.android.bbt/com.xxx.android.xxx.MainActivity)
//  序号          // 状态         // 对象                // 大小
[00:0x7be3a490e0] state=ACQUIRED 0x7be3a2eaa0 frame=471 [1080x1920:1088,  1]
[02:0x7be3a499a0] state=FREE     0x7be3a50780 frame=469 [1080x1920:1088,  1]
[01:0x7be3a492a0] state=FREE     0x7b5e883e60 frame=470 [1080x1920:1088,  1]

看下三个 Buffer 分别占用的内存:

Allocated buffers:
0x7be1bf6360: 8160.00 KiB | 1080 (1088) x 1920 |    1 |        1 | 0x20001a00 
0x7be1bf63c0: 8160.00 KiB | 1080 (1088) x 1920 |    1 |        1 | 0x20001a00
0x7be1bf6420: 8160.00 KiB | 1080 (1088) x 1920 |    1 |        1 | 0x20001a00

这部分的内存其实真的不小,特别现在手机分辨率越来越大,而且应用可能还会存在其他 Surface 的情况,例如 SurfaceView 或者 TextureView

3. Choreographer

帧率,业界一般都使用 Choreographer 来监控应用的帧率。但是需要排除页面在没有操作的情况,也就是说只在界面存在绘制的时候才做统计。

那么如何监听界面是否存在绘制行为呢?具体你可以参考《Android 之 ViewTreeObserver 全面解析》。

getWindow().getDecorView().getViewTreeObserver().addOnDrawListener

我们经常用平均帧率来衡量界面流畅度,但事实上电影的帧率才 24 帧,用户对于应用的平均帧率是 40 帧还是 50 帧并不一定可以感受出来。对于用户来说,感觉最明显的是连续丢帧情况,Android Vitals 将连续丢帧超过 700 ms 定义为冻帧,即连续丢帧超过 42 帧。

因此,我们可以统计更有价值的冻帧率。冻帧率就是计算发生冻帧时间在所有时间的占比。出现丢帧的时候,我们可以获取当前的页面信息、View 信息和操作路径进行上报,降低二次排查的难度。

另外还可以进一步细化问题,按照 Activity、Fragment 或者某个操作定义场景,通过细化不同场景的平均帧率和冻帧率,进一步细化问题排查的范围。


最后

Android 渲染框架在快速的演进,可能随着版本的升级,有些工具也不再适用;工具只是帮助我们排查问题的一种手段,比工具本省更重要的是了解和学习它们背后的工作原理,这对我们的成长会有很大的帮助。

日常开发中我们不能只满足于完成需求就可以了,在实现的同时还要多去思考例如内存、卡顿、渲染等这些影响性能的点,日积月累我们的进步自然也会更快一些。


相信你也肯定有很多好的渲染分析或其他性能优化的思路和方法,欢迎大家留言分享你的性能优化“必杀技”。文中如有不妥或有更好的分析结果,欢迎您的指正。

文章如果对你有帮助,就请留个赞吧!


扩展阅读

关于 UI 渲染,你需要了解什么?
Android 之你真的了解 View.post() 原理吗?
深入 Activity 三部曲(3)之 View 绘制流程
...

其他系列专题

Android 存储优化系列专题
Android 之不要滥用 SharedPreferences
Android 存储选项之 SQLite 优化那些事儿
Android 对象序列化之追求完美的 Serial
...

上一篇 下一篇

猜你喜欢

热点阅读