Android修炼系列(30),app卡顿的根本原因

3,643 阅读3分钟
  • 绘制任务太重,绘制一帧内容耗时不能保证在16ms内完成。
  • 主线程太忙,导致 VSync 信号来时还没准备好数据导致丢包。

那为啥是 16ms 呢?系统是怎么保证的呢?

Android 的显示过程可以简单概括为:应用层负责绘制,系统层负责渲染。应用层把经过测量、布局、绘制后的 surface 缓存数据,通过进程间通信传递到系统层服务,系统层中的 SuraceFlinger 把数据再渲染到显示屏幕上,通过 Android 的 VSYNC 刷新机制来刷新数据。

aaaa.png

这里的跨进程通信是使用的匿名共享内存 SharedClient,每个应用程序和SurfaceFlinger 都会创建一个 SharedClient,每个 SharedClient 又最多可创建31个 SharedBufferStack,而每个 SharedBufferStack 又对应着该应用程序的一个窗口。每个 SharedBufferStack 中又会包含2个或3个缓冲区,CPU/GUP 计算并渲染的数据就是来自这里,之后显示到对应窗口,下面会具体说。

总结起来就是:应用层绘制到缓存区,SurfaceFlinger 把缓存区数据渲染到屏幕,由于是两个不同进程,所以使用 Android 的匿名共享内存 SharedClient。

这张图应该很熟悉:

aaa.png

Android 系统每隔 16ms (60FPS)会发出 VSYnc 信号,来触发对 UI 进行渲染,一旦收到 VSync 信号,CPU 就开始处理各帧的数据,如果每个绘制工作都能在 16ms 内完成(对应图内的 CPU 计算 + GPU 渲染),那么就不会感觉到卡。其中 A, B 代表 SharedBufferStack 内的两个缓冲区,它们通过交换来正确显示数据,就是我们所说的双缓冲技术。

那为啥不用单缓冲呢?这会带来一个问题,假如 a 帧的数据通过 CPU/GPU 处理完毕后,驱动层会将该缓冲数据中的每个像素点都更新到屏幕上,如果采用的是单缓冲,a 帧的数据还未显示完毕呢,a+1 帧的数据又来了,缓冲区里又更新了,就会带来残影问题。

那双缓冲就完美了吗?来看另一种情况:

ccc.png

当 CPU/GPU 的处理时间超过 16ms 时,第一个 VSync 就已经到来,但缓冲区 B 的数据还没有准备好,这样就只能继续显示之前的 A 数据,从而导致丢帧现象,而后面 B 完成后,又因为没有 VSync 信号,所以 CPU/GPU 只能等待下一个 VSync 信号的来临才能继续处理下一个数据,这就导致了中间有一大段时间被浪费掉了。

所以三缓冲技术来了,来看下图:

aaaa.png

从图中可以看出,当 B 缓冲未准备好数据时,而 VSync 信号又来临了,C 缓冲区会接着处理下一帧数据,从而避免了 CPU/GPU 的浪费。但这不会解决丢帧的问题,所以这就要求我们不能让主线程“太忙”了,要避免任何有阻塞主线程的事情。

本章完,下节介绍一些优化手段和实际监控方案。

  • 参考 Android应用性能优化最佳实践:机械工业出版社