作者: http://km.oa.com/user/louiszhai , 腾讯增值服务项目管理员工
背景
为了满足日益复杂的小程序活动需求, 腾讯增值服务项目组开发了一款 Ulink 活动小程序, 该小程序以游戏社交圈为依托, 提供游戏玩家基本的社交功能, 如发帖, 评论, 点赞, 分享等.
为了支持这些功能, 进行了一系列的性能优化改进. Ulink 活动小程序共有 5 个 tab, 分别提供关注人的 feeds 信息, 所有用户的精品分享, 图文发布入口, 消息及个人页, 如下所示.
开发过程中折腾了各种各样的挑战和难题. 其中以性能问题最为棘手, 主要有体现在以下几个方面:
小程序首次访问较慢
大量 UGC 图片需要上传, 上传慢, 体验差
页面列表较长时, 滚动卡顿, 不流畅
大量图片和视频的展示, 容易导致小程序 crash
由于 tab 页较多, 内置了很多的图片素材, 请求了较多的后台接口, 以及用户发布了大图片帖子等原因, 导致小程序首次访问较慢, 体验非常糟糕.
为此, 我们首先进行了加载优化.
加载优化
加载优化的主要思路如下:
该方案分为 4 步, 分别为资源压缩, 请求合并, 延迟加载, 数据缓存.
第一步, 压缩依赖库代码, 重复使用的模块代码封装成组件, 内置本地的图片压缩大小, 以及移除掉其他不需要的页面和组件, 避免打入总包.
第二步, 小图片 base64 入包, 避免发送网络请求, 大图片利用腾讯云压缩后下载, 同时在微信小程序支持 http2 之前, 我们合并了接口请求, 提升了请求加载性能.
第三步, 优先加载首屏, 对于一些不重要的资源, 或者不会出现在首屏的图片和 canvas 画布, 进行了延迟加载, 这样就保证了首屏的速度, 对于一些性能不佳的安卓机型, canvas 画布的延迟加载, 效果比较明显.
第四步, 核心就是缓存, 减少加载, 无论是全局配置, 首屏列表信息, 还是公共图片等, 统统缓存起来, 保证首先有内容展示.
优化效果如下:
上传优化
优化完加载后, 小程序加载速度明显变快, 我们突破了第一个难题. 很快, 我们就注意到, 第 3 个 tab - 发布页成为了瓶颈, 因为这个页面可以同时上传 9 张相册照片 (可拼成九宫格), 而现在手机, 无论是 Android 还是 iOS, 随便拍一张, 可能就有几 M, 高清一点的, 就有十几二十几 M 甚至更多. 在这个基础上, 再乘以 9, 即使是 Wi-Fi 高速上传, 图片上传过程也将相当缓慢. 为了鼓励用户多发帖, 多发图, 我们必须要解决这个问题.
因此发图之前, 需要压缩图片, 压缩再压缩, 主要思路如下:
用户原始的相册图片比较大, 经过一轮 qq 和微信压缩客户端压缩后, 通常大小在 1~2M 之间, 乘以 9 后, 最坏的情况是, 有 18M 的图片需要上传, 18M 还是太大了, 为此, 引入了 canvas 画布, 通过对原图的宽高进行等比缩小, 进一步压缩图片大小, 保证单张图片的大小不超过 600k. 压缩后的多张图片, 再通过并发的请求进行上传, 最终完成发布过程.
当然, 实际过程远比这个复杂, 部分难点如下所示:
经过兼容性测试, 我们发现, 安卓部分机型绘制出来的图片背景存在黑屏, 为 canvas 添加一层白色的打底绘制后可以解决. 小程序中, 特别是安卓下, canvas 画布不能太大, 数量不能多, 为避免小程序 crash, 我们只保留一个 canvas, 因此只能一张一张的压缩, 这里就需要维持一个压缩队列. qq 小程序下, 我们还遇到了图片的 orientation 为 left 或 right 时, 图片的宽高信息互换了, 导致压缩失败, 后来在官方同学的帮助下终于解决了问题.
优化效果如下:
渲染优化
上传优化后, 单张图片的上传速度提升 71%, 图片越大, 数量越多, 叠加效果越显著, 基于此, 普通用户终于实现了发图自由. 用户发图越积极, 不仅意味着小程序越活跃, 还意味着第 2 个 tab - 发现页内容越来越多, 列表滚动越来越卡. 我们不得不重视一个问题, 那就是页面渲染存在问题. 除了滚动卡顿, 页面渲染还存在以下几个问题:
页面加载较慢
页面刷新时, 视图抖动
下拉加载时, 页面内容更新缓慢
渲染优化的主要思路如下:
小程序页面跳转时, 有个动画效果, 这个效果完成后, 才触发页面的 onLoad 回调, 可以充分利用页面切换时的间隙, 提前发送页面的请求, 从而达到预加载页面的目的. 页面切换时间大致如下:
众所周知, 小程序采用双线程模型, 如下所示:
这使得 setData 操作涉及到了线程间通信, 而频繁的 setData, 就像堵车加塞一样, 将导致两个后果:
一次 setData, 除了要进行数据传输, 还要进行一次 evaluateJavascript 脚本过程, 大量的数据, 会增加 webview JS 线程的负担. 而我们能够操控的各种点击, 滚动事件都将拥堵在 webview JS 线程上, 得不到响应. 我们不妨来看下 setData 数据量与传输时间的关系, 如下所示:
可以看到, 当数据量小于 4kb 时, 数据通信较快, 单次耗费小于 15ms. 因此不必要传输的数据, 要去掉, 特别是长列表中, 与视觉无关的数据积少成多, 愈发影响传输执行效率. 因此我们可以有几条最佳实践:
避免频繁 setData, 根本上减少通信次数
去掉 UI 无关信息, 仅更新局部的信息, 减少 setData 和 dataset 中的数据量, 减少单次通信时间
页面切后台时, 主动降低渲染优先级, 暂停 setData 操作, 避免跟前台的页面抢资源
前面我们提到了, 长列表数据量, dom 数量原本就大, 有着天然的渲染痛点, 因此滚动事件必须加以节流, 尽可能避免频繁查询节点信息, 仅更新局部可见区域的数据, 延迟更新不可见区域的视图等等. 除此之外, 页面的 canvas 画布设置为 fixed 布局, 在 iOS 下, 也会导致页面滚动卡顿, 需要改为 absolute 布局; 由于 qq 小程序的 video 同层渲染支持较晚, 早些版本的 qq 下, 页面滚动可能会导致视频错位, 或卡顿.
小程序的本质还是基于 H5, 因此 H5 的优化策略依然奏效, 比如说, 减少 dom 的节点数量, 以及层级, 需要渲染的 dom 也就少了; 事件绑定多利用委托机制, 可以减少事件响应; 而自定义的组件, 减少了数据往上传递的层级, 诸如此类的策略, 都可以为小程序渲染加速.
优化效果如下:
页面加载时间降低了 29%~41%, 性能越差的机型, 效果越明显.
优化前, FPS 帧率波动大, 平均帧率 40FPS, 最低帧率 5FPS, 优化后, FPS 帧率较稳定, 平均帧率达到了 60FPS, 最低帧率也有 50FPS.
内存优化
渲染优化后, Ulink 活动小程序整体上快了很多. 我们注意到, 发现页支持无限下拉加载, 列表可能很长, 随着用户图片增多, 有没有可能导致小程序 crash? 经过测试, 性能较差的机型再一次没有让我们失望, 不出意料的 crash 很多次. 因此, 内存问题一度成为了瓶颈.
怎么优化内存呢? 首先要节约内存, 然后还要及时释放内存.
节约内存, 图片和 canvas 懒加载是基本策略, 安卓下使用 webp 图片, 也能有效的减少 25% 左右的内存消耗, 发现页的长列表图片, 使用腾讯云压缩后, 下载到的图片尺寸大大减小, 进一步减少了内存消耗. 但无论怎么节约内存, 只要列表在加载新的图片, 内存就会增长. 因此我们动态移除了屏幕之外的图片, 改用了空白节点占位, 这个优化策略, 在列表滚动时以节流的方式执行, 最终保证了图片内存的及时释放.
优化效果如下:
优化后, 初始内存降低了 56M, 图片无限加载时, 内存基本不再变化. 不妨拿同类产品做个简单的对比, 如下所示:
可以看到, Ulink 活动小程序, 内存峰值比初始值仅高了 68M, 内存维持在 350M 左右, 而微博小程序, 内存峰值比初始值已经高了 180M, 内存还在持续增长.
以上, 是我们解决 Ulink 活动小程序性能问题的一些优化实践, 欢迎大家下方留言交流.
发现产品机会点? 试试用户分层
《动物森友会》如何以奖励设计让人喜喜爱爱?
带你了解腾讯最坚实的支撑事业群
来源: https://www.qcloud.com/developer/article/1629081