Skip to content

小程序优化

1. 性能收集

1.1 方式选择

  1. 开发者工具:开发者工具可以查看性能评分,并给出优化建议。
  2. We分析:微信小程序官方工具,可以查看小程序性能大盘,如冷启动,页面加载时间等。
  3. 自定义埋点:在小程序中添加自定义埋点,收集性能数据,如页面加载时间,接口请求时间等。

2. 收集方式

2.1 接口性能

在请求拦截器里面,统一封装逻辑,记录每个接口的请求性能,和请求正确性。
并根据配置,决定是否上报数据。

  • 收集的方式:
    • wx.request支持传入enableProfile参数,开启后会在请求完成后返回请求的性能数据。
    • 对于慢接口(如大于3000ms),则一定上报错误,并标记是否命中上报概率。
    • 对于正常接口,若命中上报概率,则上报数据。
    • 基于此规章,则可以计算出慢接口的比例,以及所有接口的性能详情。

2.2 冷启动/页面加载时间

  1. 为了做到无侵入方式收集冷启动时间,则需要重写App函数,在生命周期内收集页面性能指标。
js
// 保存原始的App函数
const originalApp = App;

// 重写App函数
App = function(appOptions) {
  // 添加性能收集逻辑
  const originalOnLaunch = appOptions.onLaunch || function() {};
  const originalOnShow = appOptions.onShow || function() {};
  
  // 记录应用启动时间
  const launchTime = Date.now();
  
  appOptions.onLaunch = function(options) {
    // 执行其他逻辑
    
    // 执行原始的onLaunch
    originalOnLaunch.call(this, options);
  };
  
  appOptions.onShow = function(options) {
      // 执行其他逻辑

      // 执行原始的onShow
    originalOnShow.call(this, options);
  };
  

  // 使用原始App创建应用
  return originalApp(appOptions);
};
  1. 调用wx.getPerformance获取性能数据。
    查看官方文档
js
const performance = wx.getPerformance()
const observer = performance.createObserver((entryList) => {
  console.log(entryList.getEntries())
})
observer.observe({ entryTypes: ['render', 'script', 'navigation'] })

3. 埋点SDK设计思路

  1. 支持不同端的上报:

    • 适配器模式:适配不同的端,如微信小程序、支付宝小程序、H5端,不同的端调用不同性能收集方式。
    • 抽象工程模式:负责根据运行环境,创建不同的适配器实例。则上报逻辑无需考虑端的差异。
  2. 支持批量上报或单条上报:

    • 若是批量上报,则维护一个上报队列,则根据一定的策略进行上报
      • 达到10条数据时上报
      • 每10秒上报一次
      • 小程序进入后台时上报
    • 若是单条上报,则直接上报数据。
    • 一般来说,如果是产品需求埋点,则采用单条上报方式,若是性能数据,则采用批量上报方式。主要是因为性能数据量大,且由于IOS无法监控“墓碑模式”,导致可能无法监控切后台操作。
  3. 可选上报的数据类型:
    上报采用websocket进行通信,提供protobuf协议进行数据传输,或原始数据传输,在开发阶段采用原始数据传输,生产环境采用protobuf协议进行数据传输,便于调试观察。

  4. 重试逻辑:

    • 若上报失败,则重试3次,每次间隔1秒。
  5. 支持上报不同的数据类型:

    • 性能数据
      • 包括冷启动时间、页面加载时间、接口请求时间等。
      • 自定义性能数据:如webview加载时间
    • 业务埋点需求
      • 由产品定义的埋点需求,若是页面访问事件,则采用无感收集。
  6. 数据报表: 将埋点数据推送到数据报表平台(如阿里云SLS平台),制作自定义报表。通过观察数据趋势或对异常数据的告警,形成闭环。

4. Sentry

4.1 集成方式

  1. SDK选择
    sentry 不支持小程序,有人提供了开源的sentry-miniapp,基于Sentry模块进行封装,以适配小程序环境。

  2. Source-map 上传 通过Taro CLI 或 miniProgram CLI, 在构建完后,将source-map文件上传到Sentry服务器。然而该source-map是不包括源码的source-map,所以不能直接在sentry里面定位源码,还需要结合开发者工具定位源码代码。

注意:一般来说,sentry使用source-map 是 hide-source-map 类型,包含原始代码

4.2 分包异步化

分包异步化 由于sentry sdk,占用了主包大小体积,所以需要将sentry sdk放在分包中。但是在分包未下载之前,无法自动上报或收集错误。
所以分包加载成功之前,需要手动搜集错误,并在分包加载成功后,再手动交给Sentry上报。

分包异步化方式

由于Taro对分包异步化支持有限。github issue

  1. 在构建时,将Sentry SDK 排除到主包外。
  2. 通过Rollup或webpack配置,将Sentry SDK独立打一个分包,使用__non_webpack_require__.async进行异步加载。

坑点:使用小程序构建小程序代码之前,一定要先保证分包已经成功打包并置于dist源码目录下。 补充说明,Taro的构建流程:源码 ——> Taro 构建 ——> dist目录下 ——> 小程序构建 ——> Taro CLI 推送最终代码到微信服务器。

4.3 告警上报

  1. Sentry支持自定义告警规则,但是前提是Sentry需要将错误分类好。所以上报时候,需要调用Sentry API将错误归类。
  2. Sentry支持配置webhook,我们可以配置飞书或钉钉的webhook,将错误信息推送到飞书或钉钉群中。
  3. Sentry支持错误统计报表,我们可以在Sentry里面定义报表,如接口超时报表、JS错误报表等。
  4. 错误闭环:Sentry上报支持自定义字段,如我们可以上报用户的SessionId,用于定位问题。

5. We分析

5.1 集成方式

We分析不需要主动集成(也可以显示调用API),在小程序内置了We分析SDK。我们只需要在we分析平台开通相关服务即可。

5.2 功能

  1. 业务数据(产品方向)
  2. 性能大盘 (页面性能,启动性能)
  3. 自定义性能:有单条上报,流程上报,模块上报三种方式,单条上报如直接上报接口性能,流程和模块上报,可以上报整个支付链路的性能
  4. 可视化日志:若开启后,小程序可以可视化查看用户的日志,为了与业务数据管理,可以调用We分析API,上报SessionId等信息。
  5. JS错误统计
  6. 实时日志:可以通过We分析API上报实时日志,便于调试和定位问题。

6. 性能调试

6.1 开发者工具

  1. 性能评分:开发者工具可以查看小程序的性能评分,并给出优化建议。
  2. 性能分析:开发者工具可以查看小程序的性能分析,包括冷启动时间、页面加载时间、接口请求时间等。
  3. 性能监控:开发者工具可以查看小程序的性能监控,包括内存使用情况。(不能看到总体数据,只能看到JS占用内存)
  4. 性能优化建议:开发者工具可以根据小程序的性能数据,给出优化建议,如减少页面渲染时间、减少接口请求时间等。

6.2 第三方App真机测试

  1. 如preDog等第三方App,可以在真机上测试小程序的性能数据。
  2. 可以准确知道小程序每个页面占用的内存。由于小程序使用的 同层渲染技术, ,还能知道小程序对宿主App(微信)的影响,如视频组件对宿主App内存的影响。

6.3. 云测试

  1. 微信云测试平台,可以在云端进行小程序的性能测试。

6. 性能优化

6.1 减少冷启动时间

  1. 使用分包: 独立分包、分包预下载、分包异步化等方式,减少主包体积。

6.2 接口优化

  1. DSN预请求
    • 在小程序管理后台,配置预请求地址,小程序在启动时会自动请求该地址,DNS获得缓存。
  2. HttpDNS
    • 购买HttpDNS服务,小程序在请求接口时会使用HttpDNS解析域名,减少DNS解析时间。
    • 实际测试下来表明,确实减少了DNS解析时间,尤其是在网络不稳定的情况下。但是增加了总体请求时间,因为HttpDNS需要先请求DNS服务器获取域名解析结果。(最终放弃)
  3. 域名合并
    • 通过域名收敛,减少DNS解析次数和建立TCP链接次数。
  4. 数据预拉取
    • 对于首页配置,每个人用户都是一样的配置,在小程序冷启动时,就可以同步请求数据。文档说明
  5. 接口缓存
    • 对于不会变更的数据,可以使用接口缓存,减少接口请求次数。(如城市数据)

6.3 页面加载优化

  1. 使用内存缓存,如将localStorage中的数据同步到内存中,减少读取localStorage的时间。
  2. 使用虚拟列表,减少页面渲染时间,尤其是列表数据较多的情况下。

6.4 代码优化

略。可以参考普通h5页面的代码优化方式,如减少DOM操作、减少JS计算量、媒体资源压缩等。

7. 一个宣传页优化的思路

我们小程序首页是一个宣传页,展示车辆卖点的宣传页。里面大概有20多屏的内容,每一屏可能是视频或者是图片,或者是轮播视频。里面还包括了动画效果,随着滚动播放动画。

第一版思路:图片采用懒加载,视频采用预加载。通过Observe监听判断目标元素距离屏幕的距离。有个阈值,当目标元素距离屏幕的距离小于阈值时,则加载该元素的资源或者开始播放动画效果。
当离开屏幕后,则停止播放动画效果,或者暂停视频播放。

但是,我们发现这样实现后,发现低端的苹果手机的小程序在频繁切换页面后,某个页面,页面所有的操作都无法响应。

然后我们发现一个规律,就是会提示内存不足,然后页面生命周期函数会触发多次,而且每次切换页面触发次数会不断累加。

这个bug,最开始以为是Taro框架的bug,通过其他小程序的测试和社区寻找,发现是IOS小程序架构的的bug,是当内存不足时,IOS会将其他页面销毁。当再回复的时候,状态无法完全恢复,导致页面生命周期函数被多次触发且事件无法响应。

然后我们就想办法测试内存到底是多少,发现开发者工具的内存只能监控JS堆栈的内存。we分析的内存监控也不准。最终我们找到了一个第三方桌面应用,可以监控小程序webview的内存和微信的内存占用。

最终发现视频组件占用了大量微信的内存,而没占用小程序的内存。

然后我们就想办法优化视频组件的内存占用。 方式1:更换了视频的编码使用了H265,去除音频轨道。 方式2:视频不在可视区域中,直接停止播放,再用封面图替换视频组件。

通过上面方式,发现有所缓解内存,但是还是会出现内存不足的情况。

第二版思路:采用虚拟列表的方式,通过虚拟列表销毁不在可视区域的元素。 然后设置每个元素块的背景色,避免滚动过快产生的白屏加载问题。

发现这个优化能显著降低内存占用,尤其是视频组件的内存占用。

但是在低端手机上会出现动画播放不流程的问题,尤其是快速滚动虚拟列表的时候卡顿更明显。

考虑到小程序的架构是双线程架构,JS线程和渲染线程分离。JS线程负责逻辑处理,渲染线程负责页面渲染。

我们通过重写setData计算每次耗时的事件,在快速滚动的时候,setData耗时很高,甚至超过了100ms。 在快速滚动的过程中,事件触发过多,JS线程无法及时处理,导致渲染线程无法及时更新页面。

然后我们就想了一个办法,将这个虚拟列表中所有的事件,合并为一个事件中心管理,事件中心维护的是一个数组,每一屏都在一个数组项里面维护,每次都是修改事件中心的状态,如果事件中心状态变更,然后每30ms再触发一次事件中心的状态。整个渲染的逻辑更新都依赖于事件中心的状态。

甚至我们做了更绝的是,低端机,直接放弃使用动画,直接显示动画终态。

后面,产品发现动画维护成本太高,每次更换宣传图,都要适配效果,然后最终又放弃了动画效果。