流加载是什么
用一句话来解释 流加载,那就是“下载程序包的同时加载包内的文件”,下载与加载并发可以加速小程序首次无缓存启动的时间。
为了提升小程序加载速度,我们做了下列改动:
流加载 1 期:实现下载与加载同步
使用 ttpkg 包替代 zip 包
在流加载方案之前,小程序的文件都是压缩到 zip 包内的,尽管 zip 包的体积更小,但 zip 包内的文件需要等完整的 zip 包下载好后才能解压出来使用,解压也需要时间。
在流加载方案中,我们改用 ttpkg 包来装载小程序的文件,ttpkg 包是未压缩过的二进制文件包,可以直接读取包内指定区域的二进制数据。ttpkg 包相比于压缩的 zip 包体积会更大,通过开启 CDN 智能压缩服务,在 ttpkg 包请求过程中,服务器对返回的 ttpkg 数据进行 gzip 编码,压缩重复的字符串等,可以减少 ttpkg 包的传输大小,从而缩短下载时间。
下图可见 ttpkg 包内的文件分布结构,仅举例,不同的小程序文件顺序可能不同。
资源加载时机提前
在 ttpkg 格式程序包的支持下,我们不必等待包下载完成后才去加载资源,资源加载的时机得以提前。
流加载优化前后的启动阶段流程分别如下:
优化前
优化后
假设某个版本的小程序 A 在改造前后包内文件不变,资源加载顺序相同,提前加载文件可以使得小程序首屏渲染更快出现。
类似的,我们可以提前加载某些不在 ttpkg 包内的文件来减少加载文件这块的耗时,比如:
- tma-core.js、tmg-core.js 预加载
- page-frame.html 预加载
- webview.js 预加载
game.js 拆分
我们还对小游戏的 game.js 文件进行了拆分,支持按需加载拆分后的 js 文件。
- 优化前:game.js 文件包含着所有游戏场景的逻辑,这在启动阶段就会全量加载到 JSC 环境中,但实际上一些加载进来的游戏逻辑尚用不到。
- 优化后: game.js 拆分出多个散文件,在 game.js 内根据散文件的使用先后顺序,边运行边按需加载,在理想情况下,不需要加载所有拆出的散文件即可渲染出首帧。
如上图,优化后从 game.js 拆出了 pageA.js、pageB.js、pageC.js、pageD.js,若该小游戏仅需要加载 pageA、pageB、pageC.js 即可渲染出首屏,那我们就省下了下载以及加载 pageD.js 的时间。
副作用
当小程序无缓存冷启动,流加载的边下边加载在特定情况下也会给我们带来副作用,比如:
- 启动阶段,ttpkg 包已下载的部分足以展示出首屏界面,但用户在操作首屏界面的过程中,忽然发生断网等问题导致 ttpkg 下载失败,此时会弹出一个报错窗口打断用户操作,用户需要重启小程序
- 用户使用阶段, ttpkg 包中的某个图片可能因为尚未下载完,而迟迟无法展示出来;或者小游戏 ttpkg 中的某个 js 文件没下载完,而发生卡顿
流加载 2 期:小程序包体改造优化
包体改造主要是优化小程序的首屏加载时间,以下方面做了优化:
app-service.js 拆分
对齐小游戏 game.js 的拆分优化,把小程序不同界面的 js 逻辑从 app-service.js 文件中拆分出来, 各界面打开时再按需加载,减少了首屏加载过程中所需要加载的内容,可缩短首屏展示时间。
如下图所示,拆分前,app-service.js 包含拆出来的 pageA、pageB、pageC 的 js 逻辑。拆分后,首屏仅需要下载完“瘦身后”的 app-service.js 以及首页 pageA.js 再加载即可。
WebView 预加载通用 page-frame.html、webview.js
除了实现流加载做到下载与加载并发来缩短首屏展示时间外,在文件加载阶段,我们会把某些特定文件提前预加载好,并且确保这些文件不在 ttpkg 包内,这样就不会被 ttpkg 下载过程所影响。
最佳理想情况下,可达到的效果图:
WebView 的创建、page-frame.html 和 webview.js 的加载都是必须的操作,所以在小程序启动前预创建、预加载好,可以一定程度减少小程序启动后再去加载他们的消耗的时间。
为了能做到预加载,流加载二期,就把 ttpkg 中的 page-frame.html(它包含所有界面的结构)精简成各界面通用的 page-frame.html, 并放到我们的 jssdk 基础库中。而 jssdk 基础库都是内置在 SDK 中的,不强依赖于下载过程。
流加载 3 期:ttpkg 包中文件分布顺序的优化
为了达到流加载的最大收益,我们还在要做 ttpkg 包中文件分布顺序的优化。
举个例子,如下是某个小游戏 ttpkg 包文件分布图,从低到高位分别安放着以下文件:
当游戏启动后,ttpkg 下载的大小刚好包含 game.js 文件,于是小游戏开始加载执行 game.js, 但 game.js 中又去加载了 gameB.js,这种情况下,小游戏就会停止等待 ttpkg 继续下载,直到包下载大小超过了 gameB.js 才会继续执行。
ttpkg 包内文件分布顺序 的优化就是为去调整文件在 ttpkg 包中的坐落位置,尽可能让文件分布顺序与小程序/游戏启动执行要加载的文件顺序一致。这样就可以减少在启动无缓存小程序的过程中, 出现要加载某个尚未下载到的文件而停止等待的场景。
后台每日根据上报的文件访问顺序埋点,按顺序以及频率进行排序,优化 ttpkg 的文件顺序,生成新的 ttpkg。该操作无需开发者做任何操作。