[QQ 导航条动画对内存的影响]
- APNG 动画优化
APNG 是一个基于 PNG 的位图动画格式,后缀名也是.png,在一些类似背景图的场景下会使用;在早期的超级调色盘中,为了实现最佳炫彩效果,选用了由 300 张 15KB 静态图合成的配色渐变的 apng 图片,其大小达到了 4.2M;不过带来的问题是渲染的延迟感;经过和设计讨论,在不影响效果体验的基础上,进行了大量的压缩,压缩到了 157KB;压缩率超过 96%。
- 聊天列表与消息
聊天列表 AIO,作为 QQ IM 模块中最主要的承载消息数据展示模块,其滚动体验必然离不开用户体验与内存的权衡。
聊天列表在静态与滚动过程中,维持消息组件的数量多少决很大程度决定整个 QQ 的内存占用。消息数据从服务端拉取后会存储在本地 DB,根据策略会将当前会话的消息数据缓存在内存中。
随着滚动加载,消息缓存占用的内存也越多,所以也有一定动态阈值的策略,丢弃滚动方向相反的旧消息,从而将内存控制在可接受范围。如果用户重新操作又需要加载时,这请求底层向本地磁盘 DB 重新拉取。
[QQ 聊天消息列表的加载策略]
消息组件实例是内存占用的大户,每条消息组件内部包含头像 / 昵称 / 状态 / 内容等多个实例,如果不对消息实例进行回收销毁,每百条消息约能带来 20M 的内存增量,因此消息实例的回收策略尤为关键。
最早版本中对消息上屏没有丢弃策略,内存增量没有很好控制。于是采用分页列表,屏内保持固定几页消息(约 30 ~ 50 条消息,视屏幕尺寸决定),超过范围的消息进行丢弃,列表高度由屏内消息直接撑起,用户通过触顶或触底进行上下一页消息的加载。
但这页带来些点问题:一方面随着触顶触底,滚动条频繁跳动的体验并不好;另一方面列表高度由不定高的组件渲染消息来维持,不得不始终保留 30 ~ 50 条消息以撑起滚动高度,不可见消息的那部分便造成内存的浪费。
使用虚拟列表维持计算高度后,列表不再依赖保持真实消息内容的渲染,理论上我们可以将可视区域以外的消息实例全部销毁,仅保留用户可见的消息,最大程度地压缩消息实例数量,指保留很少的 buffer 消息实例。在实际滚动中由于消息实例在滚动过程被不断创建和销毁,占用主线程,影响 UI 绘制和用户输入。因此我们还做了:1. 对创建销毁做一定聚合,批量处理消息上屏。2. 精简优化单条组件的渲染性能。3. 不同滚动方向调整上下不同 buffer 大小 等等措施。4、会话切换和窗口聚失焦最小化等操作时对不再使用的消息资源内存进行主动回收。
[QQ 聊天消息列表的上屏策略]
滚动性能和内存占用之间需要取得平衡,既要最大程度压缩上屏消息数量以节省内存,又要保证滚动性能体验。然而经过优化后,本地测试加载 200 条混合种类的消息场景下,从空状态进入聊天会话中,消息列表内存增量从最多 44.2M 降至 6.1M,且滚动静止后内存不会任意增长。
4)Electron 使用姿势
Electron 给主进程提供了不少对系统能力调用的 API,如托盘、系统通知、macOS 中 dock 栏设置等。但是如果对这些 Electron 能力的使用方式不对,就可能导致不必要的大量内存占用甚至是泄漏。
比如 QQ 中,我们通过短间隔定时调用 Tray setImage API 来实现 QQ 托盘的闪烁,如果不注意传入 string Path 则会每次创建 Image 对象导致内存占用,正确的方式应该创建 NativeImage 并缓存,调用 Tray setImage 传入指定 NativeImage,避免反复创建 Image 导致的内存问题。