如何实现IM即时通讯“消息”列表卡顿优化

随着移动互联网的普及,无论是IM开发者还是普通用户,IM即时通讯应用在日常使用中都是必不可少的,比如:熟人社交的某信、IM活化石的某Q、企业场景的某钉等,几乎是人人必装。

无论哪款IM,随着“消息”列表里数据量和类型越来越多,对于列表的滑动体验来说肯定会受到影响。而作为整个IM的“第一页”,这个列表的体验如何直接决定了用户的第一印象,非常重要!

有鉴于此,市面上的主流IM对于“消息”列表的滑动体验(主要是卡顿问题)问题,都会特别关注并着重优化。

对于一款 IM 软件来说,“消息”列表是用户首先接触到的界面,“消息”列表滑动是否流畅对用户的体验有着很大的影响。

随着功能的不断增加、数据累积,“消息”列表上要展示的信息也越来越多。

我们发现,产品每使用一段时间后,比如打完 Call 返回到“消息”列表界面进行滑动时,会出现严重的卡顿现象。

于是我们开始对“消息”列表卡顿情况进行了详细的分析,期待找出问题的根源,并使用合适的解决手段来优化。

到底什么是卡顿?

提到APP的卡顿,很多人都会说是因为在UI 16ms 内无法完成渲染导致的。

那么为什么需要在 16ms 内完成呢?以及在 16ms 以内需要完成什么工作?

带着这两个问题,在本节我们来深入地学习一下。

刷新率(RefreshRate)与帧率(FrameRate)

刷新率:指的是屏幕每秒刷新的次数,是针对硬件而言的。目前大部分的手机刷新率都在 60Hz(屏幕每秒钟刷新 60 次),有部分高端机采用的 120Hz(比如 iPad Pro)。

帧率:是每秒绘制的帧数,是针对软件而言的。通常只要帧率与刷新率保持一致,我们看到的画面就是流畅的。所以帧率在 60FPS 时我们就不会感觉到卡。

那么刷新率和帧率之间到底有什么关系呢?

举个直观的例子你就懂了:

    如果帧率为每秒钟 60 帧,而屏幕刷新率为 30Hz,那么就会出现屏幕上半部分还停留在上一帧的画面,屏幕的下半部分渲染出来的就是下一帧的画面 —— 这种情况被称为画面【撕裂】。相反,如果帧率为每秒钟 30 帧,屏幕刷新率为 60Hz,那么就会出现相连两帧显示的是同一画面,这就出现了【卡顿】。

所以单方面的提升帧率或者刷新率是没有意义的,需要两者同时进行提升。

由于目前大部分 Android 机屏幕都采用的 60Hz 的刷新率,为了使帧率也能达到 60FPS,那么就要求在 16.67ms 内完成一帧的绘制(即:1000ms/60Frame = 16.666ms / Frame)。

垂直同步技术

由于显示器是从最上面一行像素开始,向下逐行刷新,所以从最顶端到最底部的刷新是有时间差的。即时通讯开发

常见的有两个问题:

    1)如果帧率(FPS)大于刷新率,那么就会出现前文提到的画面撕裂;

    2)如果帧率再大一点,那么下一帧的还没来得及显示,下下一帧的数据就覆盖上来了,中间这帧就被跳过了,这种情况被称为跳帧。

为了解决这种帧率大于刷新率的问题,引入了垂直同步的技术,简单来说就是显示器每隔 16ms 发送一个垂直同步信号(VSYNC),系统会等待垂直同步信号的到来,才进行一帧的渲染和缓冲区的更新,这样就把帧率与刷新率锁定。

系统是如何生成一帧的

在 Android4.0 以前:处理用户输入事件、绘制、栅格化都由 CPU 中应用主线程执行,很容易造成卡顿。主要原因在于主线程的任务太重,要处理很多事件,其次 CPU 中只有少量的 ALU 单元(算术逻辑单元),并不擅长做图形计算。

Android4.0 以后应用默认开启硬件加速。

开启硬件加速以后:CPU 不擅长的图像运算就交给了 GPU 来完成,GPU 中包含了大量的 ALU 单元,就是为实现大量数学运算设计的(所以挖矿一般用 GPU)。硬件加速开启后还会将主线程中的渲染工作交给单独的渲染线程(RenderThread),这样当主线程将内容同步到 RenderThread 后,主线程就可以释放出来进行其他工作,渲染线程完成接下来的工作。

    1)首先在第一个 16ms 内,显示器显示了第 0 帧的内容,CPU/GPU 处理完第一帧;

    2)垂直同步信号到来后,CPU 马上进行第二帧的处理工作,处理完以后交给 GPU(显示器则将第一帧的图像显示出来)。

整个流程看似没有什么问题,但是一旦出现帧率(FPS)小于刷新率的情况,画面就会出现卡顿。

三缓冲区(Triple Buffer)

为了解决帧率(FPS)小于屏幕刷新率导致的掉帧问题,Android4.1 引入了三级缓冲区。

在双缓冲区的时候,由于 Display 和 GPU 各占用了一个缓冲区,导致在垂直同步信号到来时 CPU 没有办法进行绘制。那么现在新增一个缓冲区,CPU 就能在垂直同步信号到来时进行绘制工作。

在第二个 16ms 内,虽然还是重复显示了一帧,但是在 Display 占用了 A 缓冲区,GPU 占用了 B 缓冲区的情况下,CPU 依然可以使用 C 缓冲区完成绘制工作,这样 CPU 也被充分地利用起来。后续的显示也比较顺畅,有效地避免了 Jank 进一步的加剧。

通过绘制的流程我们知道,出现卡顿是因为掉帧了,而掉帧的原因在于垂直同步信号到来时,还没有准备好数据用于显示。所以我们要处理卡顿,就要尽量缩短 CPU/GPU 绘制的时间,这样就能保证在 16ms 内完成一帧的渲染。

卡顿问题分析

5.1 在中低端手机中的卡顿效果

有了以上的理论基础,我们开始分析“消息”列表卡顿的问题。由于 Boss 使用的 Pixel5 属于高端机,卡顿并不明显,我们特意从测试同学手中借来了一台中低端机。

A53 Processor 一般在大小核架构中当作小核来使用,其主要作用是省电,那些性能要求很低的场景一般由它们负责,比如待机状态、后台执行等,而A53 也确实把功耗做到了极致。

在三星 Galaxy A20s 手机上,全都采用该 Processor,并且没有大核,那么处理速度自然不会很快,这也就要求我们的 APP 优化得更好才行。

在有了对手机大致的了解以后,我们使用工具来查看一下卡顿点。

分析一下卡顿点

首先打开系统自带的 GPU 呈现模式分析工具,对“消息”列表进行查看。

 根据 Google 给出的颜色对应表,我们来看看耗时的大概位置。

首先我们要明确,虽然该工具叫 GPU 呈现模式分析工具,但是其中显示的大部分操作发生在 CPU 中。

其次根据颜色对照表大家可能也发现了,谷歌给出的颜色跟真机上的颜色对应不上。所以我们只能判断耗时的大概位置。

从我们的截图中可以看见,绿色部分占很大比例,其中一部分是 Vsync 延迟,另外一部分是输入处理+动画+测量/布局。

Vsync 延迟图标中给出的解释为两个连续帧之间的操作所花的时间。

其实就是 SurfaceFlinger 在下一次分发 Vsync 的时候,会往 UI 线程的 MessageQueue 中插入一条 Vsync 到来的消息,而该消息并不会马上执行,而是等待前面的消息被执行完毕以后,才会被执行。所以 Vsync 延迟指的就是 Vsync 被放入 MessageQueue 到被执行之间的时间。这部分时间越长说明 UI 线程中进行的处理越多,需要将一些任务分流到其他线程中执行。

输入处理、动画、测量/布局这部分都是垂直同步信号到达并开始执行 doFrame 方法时的回调。

这部分如果比较耗时,需要检查是否在输入事件回调中是否执行了耗时操作,或者是否有大量的自定义动画,又或者是否布局层次过深导致测量 View 和布局耗费太多的时间。

猜你喜欢

转载自blog.csdn.net/wecloud1314/article/details/125483164