弱网体验优化

在用户使用微信小程序时,可能会陷入某些网络不通畅的场景,此时一些严格依赖网络的功能可能就无法使用。

框架优化

为了让微信小程序在弱网情况下使用可以更加顺畅,微信小程序框架做了以下优化来解决弱网使用微信小程序的体验:

  1. 启动微信小程序支持异步 launch

以前启动的流程是同步 launch,同步时在弱网的时候会出现页面卡在 loading 页的情况,微信小程序框架在弱网的时候默认使用异步 launch 来优化弱网启动微信小程序的体验:

  • 同步 launch:拉取新的配置、如有新代码包会拉新代码包,再启动微信小程序;
  • 异步 launch:使用默认本地缓存的配置、代码包来启动微信小程序
  1. 支持弱网/离线一次性授权

对于 wx.getLocation 等需要用户 授权 的接口,因为授权关系会记录在后台,但是在弱网或者断网的时候请求是很难走通的,所以微信小程序框架在弱网/断网时支持一次性授权的方式来走通流程:

  • 调用授权类接口时,不管先前有无授权,都直接弹授权框走本地授权;
  • 在弱网/断网这一周期内使用该授权结果;
  • 网络恢复后清除掉一次性授权结果,重新走回向后台发请求检查授权的逻辑;

除此之外,微信小程序提供了 缓存管理器 来帮助开发者解决微信小程序弱网的问题.

目前,以下依赖网络的功能可以通过接入缓存管理器改善:

  • 纯展示类的功能
  • 只依赖部分用户授权的功能

缓存管理器

微信小程序提供了一个无侵入式的缓存管理器,开发者可以不需要修改原有业务代码进行接入。缓存管理器主要有以下几个能力:

  • 在网络通畅时,对符合规则的网络请求进行缓存;在弱网时对该网络请求使用缓存返回。
  • 在网络通畅时,对部分 wx api 调用进行缓存;在弱网时对这些 wx api 的调用使用缓存返回。

简单来说,缓存管理器可以帮助开发者在不修改微信小程序主要逻辑的情况下,快速接入缓存能力。接入过程只需要额外编写如下几行代码:

// 创建缓存管理器
const cacheManager = wx.createCacheManager({
  origin: 'https://weixin.qq.com',
})

// 添加请求规则
cacheManager.addRules([
  '/cgi/home',
  '/cgi/detail/:id',
])

// 监听符合规则的 wx.request 请求,默认在弱网时调用 wx.request 即会触发
cacheManager.on('request', evt => {
  return new Promise((resolve, reject) => {
    // 匹配是否存在缓存
    const matchRes = cacheManager.match(evt)

    if (matchRes && matchRes.data) {
      // 使用缓存返回
      resolve(matchRes.data)
    } else {
      // 没有匹配到缓存
      reject({errMsg: `catch not found: ${evt.url}`})
    }
  })
})

上述示例中使用 wx.createCacheManager 即可创建缓存管理器。缓存管理器全局只有唯一实例,一旦被成功创建出来即表示接入成功。

开发者需要添加请求规则,用来匹配哪些请求需要被缓存,不在请求规则内的请求会被自动放过。一旦请求命中规则,则在网络通畅时会对结果进行缓存,在弱网时会拦截请求,然后触发 request 事件给开发者。开发者可以在事件回调中决定是否使用缓存返回,如果使用缓存返回,则不会再发起网络请求;如果仍要尝试发起网络请求,可像如下方式操作:

cacheManager.on('request', async evt => {
  try {
    // 仍然走网络请求
    const res = await evt.request()

    // ......
  } catch (err) {
    // ......
  }
})

为了适应更多的请求场景,请求规则支持多种写法,如:

cacheManager.addRule('/abc') // uri 串,会自动使用调用 wx.createCacheManager 时传入的 origin 进行拼接,然后匹配
cacheManager.addRule('GET /abc') // 在 uri 串基础上,补充请求方法的匹配
cacheManager.addRule('/abc/:id') // 带可变部分的 uri 串

cacheManager.addRule(/\/(abc|cba)$/ig) // 正则表达式

cacheManager.addRule({
  method: 'POST',
  url: '/abc',
  dataSchema: [
    {name: 'param1', schema: {value: /(aaa|bbb)/ig}},
    {name: 'param2', schema: {value: '123'}},
  ],
}) // 规则对象

更多规则写法可参考 addRule 文档

每个命中了规则的请求,会根据一定策略生成缓存 id,如果两个请求生成的缓存 id 相同,则后者会覆盖前者,因此在编写规则时需要注意这点。一般来说,请求 url 不同或请求方法不同,生成的缓存 id 一定不同;如果请求参数不同,则需要考虑命中的规则有没有考虑参数的情况,详细的缓存 id 生成策略可参考 addRule 文档

缓存存储会使用独立的用户空间(不占用用户的 storage),不过有缓存数量和大小限制,所以也不要无节制地使用缓存。请善用规则,尽可能只让必要的请求缓存。

关于缓存管理器的详细使用方式可参考 api 文档。此处同时提供一个完整可运行的例子,参考例子的 README 进行操作即可体验。

云托管使用 cacheManager

通过 wx.cloud.callContainer 调用的接口也可以使用 wx.createCacheManager 进行弱网体验优化。缓存请求需要开发者调用 addRule 添加规则。 这里对于 addRule 参数中的 url 字段有个统一规范:https://wx.cloud.callContainer/env/servicename/path ,其中 env / servicename / path 对应 wx.cloud.callContainer 调用服务的标识字段。比如对于如下的云调用,可以按示例添加缓存规则:

const res = await wx.cloud.callContainer({
    config: {
      env: 'test-123'
    },
    path: '/api/count',
    header: {
      'X-WX-SERVICE': 'express-server',
      'content-type': 'application/json'
    },
    method: 'GET',
    data: {
      action: 'inc'
    },
})

// 添加缓存规则
cacheManager.addRule({
    url: 'https://wx.cloud.callContainer/test-123/express-server/api/count',
    method: 'get'
})

其他

部分 wx api 在接入缓存管理器后也会进行缓存,列表可参考 wx.createCacheManager 文档,开发者也可以自行调整哪些 wx api 需要缓存。

需要注意的是,如 wx.loginwx.checkSession 等接口支持缓存不等价于该接口在弱网时是可用的,缓存只是将上次成功调用的结果进行返回,接口本身的逻辑并不会改动,也就是说缓存返回中如 code 等有时效限制的内容并不会被刷新,仍然会失效。此处的缓存仅为了减少部分场景的改造成本而提供。

其他部分需要用户授权的接口/组件则会在基础库层面支持在弱网使用,用法与之前一样,开发者无需改造。

网络调优

微信小程序和小游戏网络相关 API 使用方式相同,所以我们用网络接口来统称。

网络接口的构成

网络接口主要包括四个类型:

  • request
  • download
  • upload
  • websocket

不同平台的实现

Android

  • request 接口从客户端 7.0.10 版本开始使用 Chromium 内网络相关部分封装的底层组件 (cronet),之前版本使用 HttpURLConnection 系统组件(系统组件依赖系统实现会有平台兼容性问题,我们建议用新版本微信来进行调试)。
  • download 接口从客户端 7.0.12 版本开始使用 cronet 组件,之前版本使用 HttpURLConnection 组件。
  • upload 接口目前仍在使用 HttpURLConnection 组件。
  • websocket 接口从客户端 7.0.4 版本开始使用微信底层组件 wcwss,并在 7.0.10 版本优化了调用性能。

iOS

  • request/download 接口从客户端 8.0.3 版本开始使用 cronet 组件,之前版本使用 NSURLSession 系统组件。
  • upload 接口目前仍在使用 NSURLSession 组件。
  • websocket 接口从客户端 7.0.20 版本开始使用微信底层组件 wcwss,之前版本使用 SRWebSocket 组件。

易误解的概念

success/fail/complete 回调

  • 对于 request/download/upload 接口,回调代表网络请求的最终结果。
  • 对于 websocket 接口,回调仅代表接口调用结果,应当监听其具体事件来获取真实的网络连接/请求状态。

wx.sendSocketMessage/SocketTask.send

早期单个微信小程序只允许同时存在一条 WebSocket 连接,所以老版本基础库 WebSocket 相关接口都直接设计在了 wx 上:

  • wx.connectSocket
  • wx.onSocketOpen
  • wx.sendSocketMessage
  • wx.onSocketMessage
  • wx.closeSocket
  • wx.onSocketClose
  • wx.onSocketError

现在单个微信小程序允许同时存在多个 WebSocket 连接,原有接口设计并不能满足需求,于是基础库在 1.7.0 版本之后增加了 SocketTask 的概念,通过不同的实例来管理多条连接:

  • wx.connectSocket
  • SocketTask.onOpen
  • SocketTask.send
  • SocketTask.onMessage
  • SocketTask.close
  • SocketTask.onClose
  • SocketTask.onError

原有的 wx.connectSocket 接口在新版本设计中承载了创建实例 new SocketTask 的用途,所以除了 wx.connectSocket 以外,不应该使用其它任何挂在 wx 上的 WebSocket 接口;在 wx.connectSocket 调用后,请立即同步监听 SocketTask.onOpen,否则可能会漏掉 onOpen 通知。

性能分析

Android

  • request/download 接口从客户端 7.0.12 版本开始,回调中提供了 profile 信息,给出了网络连接过程中关键时间点的耗时信息,具体含义如下:
名称 含义
redirectStart 第一个 HTTP 重定向发生时的时间。有跳转且是同域名内的重定向才算,否则值为 0。
redirectEnd 最后一个 HTTP 重定向完成时的时间。有跳转且是同域名内部的重定向才算,否则值为 0。
fetchStart 组件准备好使用 HTTP 请求抓取资源的时间,这发生在检查本地缓存之前。
domainLookUpStart DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等。
domainLookUpEnd DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等。
connectStart TCP 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间。
connectEnd TCP 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间。注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过。
SSLconnectionStart SSL 建立连接的时间,如果不是安全连接,则值为 0。
SSLconnectionEnd SSL 建立完成的时间,如果不是安全连接,则值为 0。
requestStart HTTP 请求读取真实文档开始的时间(完成建立连接),包括从本地读取缓存。连接错误重连时,这里显示的也是新建立连接的时间。
requestEnd HTTP 请求读取真实文档结束的时间。
responseStart HTTP 开始接收响应的时间(获取到第一个字节),包括从本地读取缓存。
responseEnd HTTP 响应全部接收完成的时间(获取到最后一个字节),包括从本地读取缓存。
rtt 当次请求连接过程中实时 rtt。
estimate_nettype 评估的网络状态 unknown, offline, slow 2g, 2g, 3g, 4g, last/0, 1, 2, 3, 4, 5, 6。
httpRttEstimate 协议层根据多个请求评估当前网络的 rtt(仅供参考)。
transportRttEstimate 传输层根据多个请求评估的当前网络的 rtt(仅供参考)。
downstreamThroughputKbpsEstimate 评估当前网络下载的kbps,根据最近的几次请求的rtt,回包情况,结合当前的网络情况,进行的一个网络评估结果。
throughputKbps 当前网络的实际下载kbps,根据本次请求实际计算的一个下载值,从开始请求到请求结束收到的字节数 * 8/请求耗时。
peerIP 当前请求的目标IP。
port 当前请求的目标端口。
protocol 当前请求使用的协议。
socketReused 是否复用连接。
sendBytesCount 发送的字节数。
receivedBytedCount 收到字节数。

整个请求链路为 DNS -> Connect -> SSL -> request -> response;表中 rtt 是连接过程中实时的 rtt,每个阶段都会更新,而 httpRttEstimate 和 transportRttEstimate 是结合前序请求计算的综合值。

  • websocket 接口从客户端 7.0.12 版本开始,在 onOpen 回调中提供了 profile 信息,给出了网络连接过程中关键时间点的耗时信息,具体含义如下:
名称 含义
fetchStart 组件准备好使用 SOCKET 建立请求的时间,这发生在检查本地缓存之前。
domainLookUpStart DNS 域名查询开始的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等。
domainLookUpEnd DNS 域名查询完成的时间,如果使用了本地缓存(即无 DNS 查询)或持久连接,则与 fetchStart 值相等。
connectStart 开始建立连接的时间,如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接开始的时间。
connectEnd 完成建立连接的时间(完成握手),如果是持久连接,则与 fetchStart 值相等。注意如果在传输层发生了错误且重新建立连接,则这里显示的是新建立的连接完成的时间。注意这里握手结束,包括安全连接建立完成、SOCKS 授权通过。
rtt 单次连接的耗时,包括 connect, tls。
handshakeCost 握手耗时。
cost 上层请求到返回的耗时。

整个请求链路为 DNS -> Connect;表中 connectEnd - connectStart 代表纯 tcp 连接耗时,domainEnd - domainStart 代表域名解析耗时;上述两步耗时加上 handshakeCost 代表单次连接请求的耗时。

iOS

  • request/download 接口从客户端 8.0.3 版本开始提供 profile 能力。
  • websocket 接口从客户端 7.0.20 版本开始提供 profile 能力。

提示

  • 当遇到网络问题时,除了判断网络状态是否连通外,还可以通过 rtt 来分析用户当前网络状况,用以动态调整超时参数。
  • 网络请求提供 enableProfile 参数,默认值为 true,可以通过传入 false 关闭。

优化建议

前后台切换

微信小程序切后台 5s 后,会中断网络请求,开发者会收到 interrupted 的回调,此时需要做好兼容逻辑。

网络状态变化

当用户网络状态变化时会通过事件 wx.onNetworkStatusChange 进行通知,不少网络问题是断网引起的,可以通过此事件给用户更好的提示。

弱网状态变化

基础库从 2.19.0 版本开始,提供 wx.onNetworkWeakChange 弱网变化通知,很多超时类的问题都是用户处于弱网引起的,可以通过此事件给用户更好的提示。

在最近的八次网络请求中,出现下列三个现象之一则判定弱网:

  • 出现三次以上连接超时
  • 出现三次 rtt 超过 400
  • 出现三次以上的丢包

弱网事件通知规则是:弱网状态变化时立即通知,状态不变时 30s 内最多通知一次。

request/download 新协议

从 Android 7.0.12 / iOS 8.0.3 开始,提供下面三个新参数:

名称 含义
enableHttp2 如果后台支持,尝试使用 Http2 协议
enableQuic 如果后台支持,尝试使用 Quic 协议
enableCache 缓存内容,相同请求优先读取本地内容

h2 连接速度更快,建议支持,这里需要注意 h2 的 header 是需要为全小写,打开 enableHttp2 开关前需要注意代码逻辑。

perMessageDeflate

压缩参数目前已在 Android 和 iOS 上全量支持。

问题排查

不同平台的错误返回规则

Android

cronet 的错误返回可以参考:https://chromium.googlesource.com/chromium/src/+/master/net/base/net_error_list.h

WebSocket 接口常见错误:

名称 含义
Underlying Transport Error 异常,大概率无网络引起
Timer Expired 超时,弱网或无网
The total timed out 超时,弱网或无网
TLS handshake failed tls 协商失败
TLS handshake timed tls 协商超时,可以考虑重试
Invalid HttpCode 服务器配置有误

iOS

cronet 的错误返回参考同 Android。

upload 一般返回汉语信息加上 kcferrordomaincfnetwork,可以直接在苹果开发者官网上搜索到具体的对应错误信息,协助分析解决。

ipv6 慢的问题

Android HttpURLConnection 是按照 RFC 3484 顺序尝试每个 ip 地址,这里应该是 v6 优先,但是系统尝试 v6 连接时超时就会按顺序再去尝试 v4,虽然最后也有可能在设置的 60s 超时时间内完成,但是整体耗时还是变长了,现象就是 request 接口的请求时间很长。在客户端 7.0.10 版本切换 cronet 后已经解决此问题。

证书问题

证书的注意事项已有文档说明:https://developers.weixin.qq.com/minigame/dev/guide/base-ability/network.html

  1. 证书过期或无效

可以通过 https://myssl.com/ssl.html 或其他在线工具验证,因为 Android 手机的兼容性问题,验证结果并不保证对所有 Android 机器都有效。

  1. 证书链不完整

Android 的根证书不全,如果服务器是使用中间证书,而 Android 手机上又找不到相应的根证书,就会出现相关的 SSL 错误,此时需要服务器配置完整证书链。

  1. wss 协议走 80 端口不成功

80 端口对应 http 默认不做证书校验,wss 应当选用 443 端口。

not in domain url

请求 url 不在域名列表中,遇到这个问题有几种可能:

  1. 请求 url 不在 mp 配置的域名列表里
  2. 重定向后的 url 不在域名列表里
  3. websocket 请求的端口没有配置
  4. 配置的域名未生效(极低概率)

network is down

iOS 14 系统新增了本地网络开关,如果关闭则局域网不通,系统接口报错 network is down,目前系统未提供检测开关方法,开发者需要根据错误信息提示用户打开权限。

接口调用频率规范

概念介绍

微信小程序wx接口可分为“普通接口”和“限频接口”。

“限频接口”指的是一个用户在一段时间内不允许频繁调用的wx接口,此类接口一般会调用到微信后台系统资源,为了保护系统,同时防止用户资源被滥用,开发者需要对此类接口做适度的频率限制,不能无节制地调用。

平台会对微信小程序内“限频接口”的调用情况做监控,如果微信小程序对此类接口的调用频率超出平台的规范,将会收到站内信提醒。系统会在资源紧张的情况下优先保障合理使用的微信小程序的服务。

开发者可登录微信小程序管理后台-开发管理-接口设置中查看“限频接口”调用情况。

目前,“限频接口”包括以下接口:

  1. wx.login
  2. wx.checkSession
  3. wx.getSetting
  4. wx.getUserInfo
  5. wx.getUserProfile

频率规范

API 规范 其他说明
wx.login 一天的调用总次数不多于该微信小程序pv的两倍,单用户一秒钟不能大于4次
wx.checkSession 一天的调用总次数不多于该微信小程序pv的两倍,单用户一秒钟不能大于4次
wx.getSetting 一天的调用总次数不多于该微信小程序pv的两倍,单用户一秒钟不能大于4次
wx.getUserInfo 一天的调用总次数不多于该微信小程序pv的两倍,单用户一秒钟不能大于4次
wx.getUserProfile 一天的调用总次数不多于该微信小程序pv的两倍,单用户一秒钟不能大于4次

Tips: 微信后台会延迟一天统计上一天的微信小程序pv总数和api调用总数,超过规范总数的会提醒尽快调整。

优化方法

开发者可以参考以下方法对“限频接口”的调用频率做优化:

  • 把上一次调用接口的返回结果缓存下来以供后续逻辑复用,而不是重新调用接口
  • 避免在定时循环的逻辑内重复调用“限频接口”
  • 避免在页面初始化事件onLoadonShowonReady中调用限频接口,应该在微信小程序初始化事件onLaunch中调用

以下是错误用法和正确用法示例:

  • wx.getSetting 错误用法:
setInterval(() => {
  wx.getSetting()
}, 5000)
  • wx.getSetting 正确用法:
let setting
wx.getSetting({
  success(res) {
    setting = res
  }
})

// 在需要获取地理位置时
if (setting.authSetting['scope.userLocation']) {
  wx.getLocation({
    success(res) {},
    fail(res) {
      if (res.errMsg.indexOf('auth deny') >= 0) {
        // 如果权限没有开,引导用户打开设置页开启地理位置授权
      }
    }
  })
}
  • wx.getUserInfo 错误用法:
Page({
  onShow() {
    wx.getUserInfo()
  }
})
  • wx.getUserInfo 正确用法:
App({
  onLaunch() {
    wx.getUserInfo()
  }
})

WXWebAssembly

WXWebAssembly 类似于 Web 标准 WebAssembly,能够在一定程度上提高微信小程序的性能。

从基础库 v2.13.0 开始,微信小程序可以在全局访问并使用 WXWebAssembly 对象。

从基础库 v2.15.0 开始,微信小程序支持在 Worker 内使用 WXWebAssembly。

WXWebAssembly.instantiate(path, imports)

和标准 WebAssembly.instantiate 类似,差别是第一个参数只接受一个字符串类型的代码包路径,指向代码包内 .wasm 文件

与 WebAssembly 的异同

  1. WXWebAssembly.instantiate(path, imports) 方法,path为代码包内路径(支持.wasm和.wasm.br后缀)
  2. 支持 WXWebAssembly.Memory
  3. 支持 WXWebAssembly.Table
  4. 支持 WXWebAssembly.Global
  5. export 支持函数、Memory、Table,iOS 平台暂不支持 Global

其他说明

  • 关于 WebAssembly 的文档可以参考 https://webassembly.org/
  • 基础库 v2.14.0 之后,新增了一些 WXWebAssembly 特性
    • 代码包路径允许传入 brotli 压缩的 wasm 文件,如 .wasm.br
    • 增加对 WXWebAssembly.Global 的支持
  • 微信小程序插件从基础库 v2.18.1 开始支持 WXWebAssembly
  • 在 Worker 内使用 WXWebAssembly 时,.wasm 文件需要放置在 worker 目录外,因为 worker 目录只会打包 .js 文件,非 .js 文件会被忽略
  • 从微信 8.0.25 开始支持 SIMD 特性

最佳实践

1. 避免JS异常

出现 JavaScript 异常可能导致程序的交互无法进行下去,我们应当追求零异常,保证程序的高鲁棒性和高可用性。

得分条件:不出现任何JS异常

2. 避免网络请求异常

请求失败可能导致程序的交互无法进行下去,应当保证所有请求都能成功。

得分条件:所有已授权网络请求都正常返回,未授权网络请求需要给出 401 或 403 这两种状态码

3. 不使用废弃接口

使用即将废弃或已废弃接口,可能导致微信小程序运行不正常。一般而言,接口不会立即去掉,但保险起见,建议不要使用,避免后续微信小程序突然运行异常。

得分条件:不使用任何文档中提示废弃的接口

4. 使用HTTPS

使用HTTPS,可以让你的微信小程序更加安全,而HTTP是明文传输的,存在可能被篡改内容的风险

得分条件:所有网络请求都使用HTTPS

5. 避免setData数据冗余

setData操作会引起框架处理一些渲染界面相关的工作,一个未绑定的变量意味着与界面渲染无关,传入setData会造成不必要的性能消耗。

得分条件:setData传入的所有数据都在模板渲染中有相关依赖

6. 最低基础库版本

当使用的组件/API 的支持版本大于配置的线上最低基础库版本时,可能导致相应功能不可用。开发者可通过调整最低基础库版本或在代码上兼容的方式解决该问题。

由于用户可以通过代码兼容的方式解决该问题,因此该指标仅作为评分的提醒项,不计入总分中。

判断标准:不存在使用的组件/API 的支持版本大于配置的线上最低基础库版本

7. 移除不可访问到的页面

微信小程序的包大小会影响加载时间,应该尽量控制包体积大小,避免将不会被使用的文件打包进去。

由于该项指标依赖开发者的操作路径,因此仅作为评分的提醒项,不计入总分中。

判断标准:不存在访问不到的页面被打包到微信小程序中

8. WXSS使用率

我们应该按需引入 wxss 资源,如果微信小程序中存在大量未使用的样式,会增加微信小程序包体积大小,从而在一定程度上影响加载速度。

由于该项指标依赖开发者的操作路径,因此仅作为评分的提醒项,不计入总分中。

判断标准:每个 wxss 资源的未使用部分不超过 2KB

9. 及时回收定时器

定时器是全局的,并不是跟页面绑定的,当微信小程序从一个页面路由到另一个页面之后,前一个页面定时器应注意手动回收。

由于该项指标依赖开发者的操作路径,因此仅作为评分的提醒项,不计入总分中。

判断标准:所有定时器的回调执行时所在的页面都与设置定时器的页面一致

体验

1. 开启惯性滚动

惯性滚动会让滚动更顺畅。在安卓下默认有惯性滚动,而在 iOS 下需要额外设置-webkit-overflow-scrolling: touch的样式;

得分条件:wxss中带有overflow: scroll的元素,在 iOS 下需要设置-webkit-overflow-scrolling: touch样式

2. 避免使用:active伪类来实现点击态

使用 css :active伪类来实现点击态,很容易触发,并且滚动或滑动时点击态不会消失,体验较差。建议使用微信小程序内置组件的 ‘hover-class’ 属性来实现

得分条件:不使用:active伪类,并使用hover-class替换:active

3. 保持图片大小比例

图片若没有按原图宽高比例显示,可能导致图片歪曲,不美观,甚至导致用户识别困难。可根据情况设置 image 组件的 mode 属性,以保持原图宽高比。

得分条件:显示的高/宽与原图的高/宽不超过 15%

4. 可点击元素的响应区域

我们应该合理地设置好可点击元素的响应区域大小,如果过小会导致用户很难点中,体验很差。

得分条件:可点击元素的宽高都不小于 20px

5. iPhone X 兼容

对于position: fixed的可交互组件,如果渲染在iPhone X的安全区域外,容易误触 Home Indicator,应当把可交互的部分都渲染到安全区域内。

建议使用以下wxss进行兼容

padding-bottom: constant(safe-area-inset-bottom);
padding-bottom: env(safe-area-inset-bottom);

得分条件:position: fixed且高度小于 68px 的可交互组件渲染在安全区域内

6. 窗口变化适配

对于支持调整大小的页面,需要对不同窗口尺寸进行UI适配,以达到良好体验。

可以使用 match-media 组件、MatchMediaObserver 或者 @media 媒体查询对页面补充适配逻辑。

得分条件:支持调整大小的页面有相关适配逻辑

7. 合理的颜色搭配

文字颜色与背景色需要搭配得当,适宜的颜色对比度可以让用户更好地阅读,提升微信小程序的用户体验。

由于颜色搭配的计算方法较为复杂,目前算法还在不断优化中。因此该指标仅作为评分的提醒项,不计入总分中。

判断标准:

1. 对于较大字体(font-size >= 24px,或同时满足font-size >= 19pxfont-weight >= 700),文字颜色和背景颜色的对比度不小于3

2. 其他字体,文字颜色和背景颜色的对比度不小于4.5

对比度计算方法参考W3C标准

性能

1. 首屏时间

首屏时间是指用户从打开微信小程序看到第一屏主要内容的时间,首屏时间太长会导致用户长时间看到的都是白屏,影响使用体验。

优化首屏时间,可以分为以下几种情况:

  1. 首屏渲染的内容较多,需要集合多份数据进行渲染。这种情况需要开发者把内容分优先级,把优先级高的内容做优先展示,缩短白屏时间;
  2. 首屏内容依赖的数据从服务端请求的时间太长。开发者需要从服务端侧具体分析服务端数据返回的时间长的原因;
  3. 一次性渲染数据太大或依赖的计算过于复杂。减少渲染的数据量、优化渲染相关数据的算法可以解决这类问题。

得分条件:首屏时间不超过 5 秒

2. 渲染时间

渲染时间指的是首次渲染或因数据变化带来的页面结构变化的渲染花费的时间。

渲染界面的耗时过长会让用户觉得卡顿,体验较差,出现这一情况时,需要校验下是否同时渲染的区域太大(例如列表过长),或渲染依赖的计算是否过于复杂。

得分条件:渲染时间不超过 500ms

3. 脚本执行时间

脚本执行时间是指JS脚本在一次同步执行中消耗的时间,比如生命周期回调、事件处理函数的同步执行时间。

执行脚本的耗时过长会让用户觉得卡顿,体验较差,出现这一情况时,需要确认并优化脚本的逻辑

得分条件:一个执行周期内脚本运行时间不超过 1 秒

4. setData调用频率

setData接口的调用涉及逻辑层与渲染层间的线程通信,通信过于频繁可能导致处理队列阻塞,界面渲染不及时而导致卡顿,应避免无用的频繁调用。

得分条件:每秒调用setData的次数不超过 20 次

5. setData数据大小

由于微信小程序运行逻辑线程与渲染线程之上,setData的调用会把数据从逻辑层传到渲染层,数据太大会增加通信时间。

得分条件:setData的数据在JSON.stringify后不超过 256KB

6. WXML节点数

建议一个页面使用少于 1000 个 WXML 节点,节点树深度少于 30 层,子节点数不大于 60 个。一个太大的 WXML 节点树会增加内存的使用,样式重排时间也会更长,影响体验。

得分条件:页面WXML节点少于 1000 个,节点树深度少于 30 层,子节点数不大于 60 个

7. 图片缓存

开启 HTTP 缓存控制后,下一次加载同样的图片,会直接从缓存读取,大大提升加载速度。

得分条件:所有图片均开启 HTTP 缓存

8. 图片大小

图片太大会增加下载时间和内存的消耗,应根据显示区域大小合理控制图片大小。

得分条件:图片宽高乘积 <= 实际显示宽高乘积 * (设备像素比 ^ 2)

9. 请求耗时

请求的耗时太长会让用户一直等待甚至离开,应当优化好服务器处理时间、减小回包大小,让请求快速响应。

得分条件:所有网络请求都在 1 秒内返回结果

10. 网络请求数

短时间内发起太多请求会触发微信小程序并行请求数量的限制,同时太多请求也可能导致加载慢等问题,应合理控制请求数量,甚至做请求的合并等。

得分条件:通过wx.request发起的耗时超过 300ms 的请求并发数不超过 10 个

11. 图片请求数

短时间内发起太多图片请求会触发浏览器并行加载的限制,可能导致图片加载慢,用户一直处理等待。应该合理控制数量,可考虑使用雪碧图技术或在屏幕外的图片使用懒加载。

得分条件:同域名耗时超过 100ms 的图片请求并发数不超过 6 个

12. 网络请求缓存

发起网络请求总会让用户等待,可能造成不好的体验,应尽量避免多余的请求,比如对同样的请求进行缓存

得分条件:3 分钟以内同一个url请求不出现两次回包大于 128KB 且一模一样的内容

评分方法

目前体验评分共有27条规则,共分为三类:性能、体验、最佳实践,满足规则要求得分(100分),否则不得分(0分),最后根据各规则权重和公式计算出总得分。

权重为0的规则,表示该规则不参与评分,仅作为提示项。开发者可在开发者工具中可以点击“忽略”。

各规则的得分条件也可能会随微信小程序的版本更新有一定的调整。

权重如下表

分类 规则 权重
性能 脚本执行时间 7
首屏时间 6
渲染时间 6
setData调用频率 6
setData数据大小 6
WXML节点数 6
请求耗时 5
网络请求数 5
图片请求数 5
图片缓存 4
图片大小 4
网络请求缓存 2
体验 开启惯性滚动 8
避免使用:active伪类来实现点击态 8
保持图片大小比例 4
可点击元素的响应区域 3
iPhone X兼容 3
窗口变化适配 3
合理的颜色搭配 0
最佳实践 避免JS异常 3
避免网络请求异常 3
废弃接口 2
使用HTTPS 1
避免setData数据冗余 1
最低基础库版本 0
移除不可访问到的页面 0
WXSS使用率 0
及时回收定时器 0

规则说明

详细的规则说明可参考下列文档:

  • 性能
  • 体验
  • 最佳实践

体验评分

体验评分是一项给微信小程序的体验好坏打分的功能,它会在微信小程序运行过程中实时检查,分析出一些可能导致体验不好的地方,并且定位出哪里有问题,以及给出一些优化建议。

基础库 3.7.0 版本推出了微信小程序性能诊断工具,作为体验评分的升级,可直接在真机进行性能测试。

运行环境要求

  • 下载并安装 1.02.1808300 或以上版本的开发者工具,下载地址。
  • 基础库需要切到 2.2.0 或以上版本。

使用流程

  1. 打开开发者工具,在详情里切换基础库到 2.2.0 或以上版本。
  2. 在调试器区域切换到 Audits 面板。
  3. 点击”开始“按钮,然后自行操作微信小程序界面,运行过的页面就会被“体验评分”检测到。

start

  1. 点击 “停止” 则结束检测,在当前面板显示相应的检测报告,开发者可根据报告中的建议对相应功能进行优化。
  2. 如需再次运行体验评分,可点击报告上方的“清空体验评分”恢复初始状态。请注意,目前系统不提供报告存储服务,一旦清空体验评分,将无法再查看本次评分结果。

start

自动运行

为了方便开发者能够及时发现微信小程序的体验问题,从开发者工具 1.02.1811150 版本起支持体验评分的 “自动运行” 功能。

该功能会在开发调试微信小程序时,实时检查,一旦发现体验分数低于 70 分时,系统会在 console 面板打印一个 warning 信息提示开发者,此时开发者可以切到 Audits 面板查看详情。

开发者在工具的右上角 “详情” 面板的 本地设置 中勾选 “自动运行体验评分” 选项即可开启。

autorun

评分规则

具体的评分细则和详情的规则说明可参考下列文档:

  • 评分方法
  • 性能
  • 体验
  • 最佳实践

性能面板

从微信 6.5.8 开始,我们提供了性能面板,让开发者了解微信小程序的性能。开发者可以在开发版微信小程序下打开性能面板,打开方法:进入开发版微信小程序,进入右上角更多按钮,点击「显示性能窗口」。

image

性能面板指标说明

指标 说明
CPU 微信小程序进程的 CPU 占用率,仅 Android 下提供
内存 微信小程序进程的内存占用(Total Pss),仅 Android 下提供
启动耗时 微信小程序启动总耗时
下载耗时 微信小程序包下载耗时,首次打开或资源包需更新时会进行下载
页面切换耗时 微信小程序页面切换的耗时
帧率/FPS
首次渲染耗时 页面首次渲染的耗时
再次渲染耗时 页面再次渲染的耗时(通常由开发者的 setData 操作触发)
数据缓存 微信小程序通过 Storage 接口储存的缓存大小