字节小程序
开发者社区
小程序小游戏
登录
小程序数据预取,从此省去加loading的烦恼!

小程序数据预取,从此省去加loading的烦恼!

5245浏览作者: falafala

小程序页面的加载会涉及到网络请求,这些请求往往需要小程序启动后才能触发,这样就会出现下面视频中的情况,明明小程序已经启动了,因为请求没有返回,依然需要显示loadingview(图中大风车)来掩盖没有加载完全的页面。


为了改善这种情况,我们推出了数据预取功能,接入后就可以完全省去loadingview的展示,启动后就能直接展示页面内容了。下面我对这个功能做一个简单的介绍

数据预取功能介绍

通常来说,小程序的请求都是在运行时以tt.request的方式调用,并返回的。在运行时小程序可以对用户可能触发的下一步流程针对性的去进行数据的提前获取。但是如果这个页面是首页,小程序内能做的就不多了,所以小程序数据预取就应运而生啦!

小程序数据预取的主要功能就是在启动之前,提前按照小程序配置好的规则发起网络请求并缓存,这样在小程序启动后就可以直接使用缓存数据,来避免因为等待数据返回而出现的loadingview或页面展示不全。简单的流程如下:


这个功能后面线上开了AB实验,以西瓜视频小程序为例,大盘FMP从1173ms优化到818ms,整体优化300+ms,速度提升了30%!


👀是不感觉心动啦!我们下面就来介绍如何使用这个功能~

参数配置

规则

首先我们需要在app.json中配置数据预取的规则,字段为prefetchRules。为了防止一些特殊情况,我们对页面个数,以及单个页面的请求个数都做了限制。最多配置10 个页面,每个页面最多5 个预取请求。先看一个示例:

// app.json
{
    "prefetchRules": {
        "pages/index/index": {
            "https://developer.bytedance.com/${search}?a=${a}&b=2&c=3": {
                "method": "POST",
                "header": {
                    "a": "${a}",
                    "token": 1 
                },
                "data": {
                    "a": "${a}", 
                    "token": 1 
                },
                "responseType": "",
                "hitPrefetchExtraRules": { // 补充命中规则,只要包含以下key就算命中
                    "requiredQueryKeys": ["a", "b", "c"], // 不考虑顺序
                    "requiredHeaderKeys": [​"a", "token"]
                }
            },
            ...
        }
    }
}

看起来是不是有一些复杂,我们来拆解一下,其实很容易理解的:

填充规则

填充规则负责描述请求的配置

"pages/index/index": {
    "https://developer.bytedance.com/${search}?a=${a}&b=2&c=3": {...}
    "https://developer.bytedance.com/${search}?a=${a}&b=2&c=5": {...}
}

配置第一层

  • key:页面路径
  • value:当前路径对应的预取请求配置列表
    • key:发起的请求url,可以进行动态化的配置
    • value:请求的配置

核心就在于${XXX}变量,每个变量都可以被填充,当所有的变量被数据源填充后,就是一个合格的预取请求了。至于变量的填充方式,我们后面会说。目前我们只支持path以及query的配置,host是不支持的。

"method": "POST",
"header": {
    "a": "${a}",
    "token": 1 
},
"data": {
    "a": "${a}", 
    "token": 1 
} ,
"responseType": "",

然后这部分看起来大家应该比较熟悉,这些就是对单个请求的配置,这部分配置项不是必要的,每项都有对应的默认值,具体规则见下表:

参数名

数据类型

是否必传

默认值

说明

header

object

optional

{'content-type': 'application/json'}

请求header

method

string

optional

GET

请求方法,支持 GET ,POST

data

string/object

optional

null

请求数据,命中需完全一致(不支持GET请求,会替换为空字符串)

responseType

string

optional

text

响应数据类型,可选值:text

匹配规则

匹配规则负责在缓存命中判断时提供额外的判断条件

因为上述填充规则描述的配置项基于一个请求来说并不算少,所以如果需要完全匹配才算命中的话,有些严苛了,以至于会拉低整体的命中率。所以这里我们引入了模糊匹配的规则hitPrefetchExtraRules,可以进一步提升缓存的命中率

"hitPrefetchExtraRules": { // 补充命中规则,只要包含以下key就算命中
    "requiredQueryKeys": ["a", "b", "c"], // 不考虑顺序
    "requiredHeaderKeys": [​"a", "token"]
}

requiredQueryKeysrequiredHeaderKeys规则描述:

  1. 该字段的值为一个字符串数组
  2. 该字段不存在时,命中规则为严格匹配,只有在tt.request 请求的配置和预取请求的配置,完全相同时才会命中
  3. 当该字段存在时,在tt.request 请求的配置和预取请求的配置在对比query和header时,只会比较在requiredQueryKeys,requiredHeaderKeys中指定的key和key对应的value,其他值不做比较。
// 预取请求
https://developer.bytedance.com/search?a=1&b=2&c=3
// 命中规则
{ "requiredQueryKeys": ["a", "b", "c"] }
// tt.request 请求
https://developer.bytedance.com/search?a=1&b=2&c=3&d=4 // 命中
https://developer.bytedance.com/search?a=1&b=2&c=4 // 不命中
https://developer.bytedance.com/search?a=1&b=2&c=4&d=4 // 不命中

以上就是关于规则的描述,但是如何把规则转化成真实的请求?我们需要把其中的变量填充,而填充变量的来源就是数据源。

数据源

数据源作为请求中变量对应的真实数据,目前支持schema中start_page的querystorage变量。

sslocal://microapp?version=v2&app_id=ttef9b992670b151ec&scene=023001&version_type=current&bdp_log=%7B%22location%22%3A%22in_video_tag%22%2C%22launch_from%22%3A%22homepage_hot%22%7D&tech_type=1&start_page=pages%2Findex%2Findex%3Fid%3Dtest&bdpsum=5c615c2

上面是小程序schema的示例,其中黑体的部分作为小程序的启动页面,经过解码后变为:

start_page=pages/index/index?id=test

pages/index/index 对应规则中的页面路径,后面的query则作为数据预取的数据源。

// app.json
{
    "prefetchRules":{
        //页面路径
        "pages/index/index": {
            //请求url
           "https://developer1.bytedance.com?spuid=${id}":{},
           "https://developer2.bytedance.com":{},
           "https://developer3.bytedance.com":{},
           ...
        }
        ...
    }
}

使用上述规则中的例子,请求会被构建为“https://developer1.bytedance.com?spuid=test”。

query变量特性

优点:统一

缺点:无法针对单机,无法使用基础变量

storage变量特性

优点:可以使用持久化的数据,可以使用运行时的数据

缺点:首次打开,变量不存在,无法使用

生效顺序:schema query变量,localStorage(优先级逐渐变低)

好的,经过以上的配置与数据的填充,我们可以构造出合格的数据预取请求了,但是预取的目的其实是为了缓存复用。下面我们来介绍最后一步,缓存的复用。

缓存复用

预取后的缓存会在程序调用tt.request发网络请求时判断是否可用,如果可用则直接返回缓存数据

tt.request({
  url: "https://developer.bytedance.com",
  usePrefetchCache: true,
  success: (res) => {
    console.log("返回数据是否来自预取:", res.isPrefetch);
    console.log("请求数据:", res.data);
  },
});

其中红色标注的字段“usePrefetchCache”为开发者对于是否期望消费缓存的配置。

如果开发者期望使用预取缓存,我们会基于配置中的规则,对数据项一一对比,并在结果回调中标注请求结果数据是否来自预取缓存。对于进行中的数据预取,我们会复用请求,这类请求也算预取缓存复用。

触发时机

最后我们来聊聊触发时机,目前的触发时机有两种:

  1. 在用户点击后到小程序onlaunch之间,会根据打开小程序的schema触发数据预取
  2. 在小程序入口曝光后,小程序包下载完成后,会根据预下载schema触发(内测功能,后面会开放权限申请,预估服务端压力最高可达正常启动的50倍

例,在抖音刷到带POI锚点的视频,除了触发小程序包的预下载,也会根据下载的schema触发数据预取,这部分数据预取的内容会存储在内存中,如果5分钟内不消费,就会释放。如果期间小程序有启动,则会随着小程序内存释放。

由于预下载的触发量会比较大,如果配置了数据预取会触发大量请求,需要服务端有一定抗压能力。我们之前在今日头条小程序增加了这个入口的预取,但是由于入口配置错误,导致预取请求在错误入口的流量突增,触发了服务端的异常流量告警,后面oncall找过来才纠正了配置。


更多数据预取能力说明可查看:数据预取

最后,欢迎大家接入数据预取,让小程序再无loading~

最后一次编辑于 2021年11月15日
加载中