英文原文:Improving Comment Rendering on Android
上周,成千上万来自全世界的 IG 用户齐聚在社区组织的先下聚会 Worldwide InstaMeet11 上。WWIM11 是历史上最大,最具地域多样性的 Instagram 聚会,从 Muscat 到 Bushwick,成千上万用户分享了大约 10 万张照片。
每月世界上有超过 3 亿用户每月使用 IG,其中 65% 来自美国以外的国家,无论用户在哪,我们一致致力于让 IG 更快,更容易使用。自从去年夏天 IG 重新设计后,我们在继续努力提升性能。
我们最近的一项改进是关于渲染庞大复杂的文本以及如何通过改进它优化 IG 的 feed 滚动。我们希望你可以从我们的经验中找到提升自己 app 速度的方法。
产品需求和性能问题
在 IG 中,feed 是由图片,视频和文字组成的。对于每个图片和视频,我们需要展示对应的图片说明和 5 条最近的评论。由于用户通常通过图片说明来讲书图片背后的故事,这些图片说明通常是大段复杂的文字,甚至可能包含链接和 emoji 表情。
渲染这种复杂文本的主要问题在于它滚动时对性能的影响。在 Android 中,文本的渲染是很慢的。即使在一个像 Nexus 5 这样的新设备上,一段有十几行复杂文本的图片说明的初始绘制时间可能会达到 50ms,而其文本的 measure 阶段就需要 30ms。这些都发生在 UI 线程,在滚动时会导致 app 跳帧。
使用 text.Layout,缓存 text.Layout
Android 有很多用于文字展示的控件,但实际上,他们都用 text.Layout 进行渲染。例如,TextView 会将 String 转化为一个 text.Layout 对象,并通过 canvas API 将它绘制到屏幕上。
由于 text.Layout 需要在构造函数中测量文本的高度,因此它的创建效率不高。缓存 text.Layout 和复用 text.Layout 实例可以节省这部分时间。Android 的 TextView 控件并没有提供设置 TextLayout 的方法,但是添加一个这样的方法并不困难:
使用自定义的 view 来手动绘制 text.Layout 会提升其性能:TextView 是一个包含大量特性的通用控件。如果我们只需要在屏幕上渲染静态的,可点击的文本,事情就简单多了:
通过使用 TextLayoutView,我们可以缓存和复用 text.Layout,从而避免了每次调用 TextView 的 setText (CharSequence c)方法时都要花费 20ms 来创建它。
下载 feed 后准备好 Layout 缓存
由于我们确定会在下载评论后展示他们,一个简单的改进是在下载它们后就准备好 text.Layout 的缓存。
停止滚动后准备好 TextLayoutCache
在可以设置 text.Layout 缓存后,我们的到来常数级的测量(measure)和绑定(binding)时间。但是初次绘制的时间仍然很长。50ms 的绘制时间可能会导致明显的卡顿。
这 50ms 中的大部分被用于测量文本高度以及产生文字符号。这些都是 CPU 操作。为了提升文本渲染速度,Android 在 ICS 中引入了 TextLayoutCache 用于缓存这些中间结果。TextLayoutCache 是一个 LRU 缓存,缓存的 key 是文本。如果查询缓存时命中,文本的绘制速度会有很大提升。
在我们的测试中,这种缓存可以将绘制时间从 30ms-50ms 减少到 2ms-6ms。
为了更好的提升绘制性能,我们可以在绘制文本到屏幕前准备好这个缓存。我们的思路是在一块屏幕外的 canvas 上虚拟的绘制这些文本。这样在我们绘制文本到屏幕前,TextLayoutCache 就已经在一个背景线程中被准备好了。
默认情况下,TextLayoutCache 的大小为 0.5M,这足以缓存十几张图片的评论。我们决定在用户停止滑动时准备缓存,我们向用户滑动的方向提前缓存 5 个图片的评论。在任何时候,我们都至少在任何一个方向上缓存了 5 个图片的评论。
在应用了所有的这些优化后,掉帧情况减少了 60%,而卡顿的情况减少了 50%。我们希望这些能帮助你提升你 app 的速度和性能。告诉我们你的想法吧,我们期待听到你的经验。