之前参加2020年微信小程序应用开发大赛的时候写了一个用于校园足球的微信小程序——踢在浙大。

    在小程序的设计过程中出现了一个裁判工具的功能,简单地说就是裁判在比赛过程中使用裁判工具中秒表的功能,来记录在第几分钟发生了什么事情。举个例子,如下图就是比赛过程中记录的比赛事件。

    q.jpg

    因为当时刚开始学习JavaScript,所以实际上有很多的内容一知半解甚至根本不懂但就要开始实现这样一个任务。在网上搜集了大量的资料之后最终终于发现可以使用 setInterval 来实现我所需的功能。setIntervalsetTimeout的使用差别不大,参数都是一样的。区别就在于setTimeout是到时执行一次,setInterval是根据设置的时间来回调的,比如每秒回调一次。

    首先对开启计时的功能进行编写,开始计时就是将各个初始值归零并进行 setInterval的初始化设置。

    1. // 比赛开始,设置计时开始
    2. begin: function () {
    3. var that = this
    4. var app = getApp()
    5. clearInterval(intt); // 设置停止(暂停),时间重置,从00:00开始
    6. that.setData({
    7. minute: this.data.minute,
    8. second: this.data.second,
    9. millisecond: this.data.millisecond,
    10. timecount: this.data.minute + ':' + this.data.second,
    11. })
    12. intt = setInterval(function () { that.timer() }, 50); // 每 50 ms回调一次
    13. },

    接下来实现一些比较简单的功能,例如暂停计时,进入到下半场的时间(我校学生足球比赛为80分钟一场,所以半场时间为40分钟),计时器清零等。

    1. // 暂停计时
    2. stop: function () {
    3. clearInterval(intt);
    4. },
    5. // 进入下半场计时
    6. Reset: function () {
    7. var that = this
    8. clearInterval(intt);
    9. this.setData({
    10. minute: 40,
    11. second: 0,
    12. millisecond: 0,
    13. timecount: 40 + ':' + '0' + 0,
    14. })
    15. },
    16. // 清零计时器
    17. zero: function () {
    18. clearInterval(intt);
    19. this.setData({
    20. minute: 0,
    21. second: 0,
    22. millisecond: 0,
    23. timecount: '0' + 0 + ':' + '0' + 0,
    24. })
    25. },

    最核心的函数功能也就是计时器功能。计时器的函数主要需要解决的问题就是每次给 millisecond 增加 5,也就是每次增加 5ms ,再通过setInterval来反复回调进行该过程,因为每次只增加 5ms,所以理论上计时相对精确。同时,计时器还需要有时间进位的一些规则因为一场比赛官方时间应该是 00:00 —— 80:00 ,但实际上经过补时之后一般会在00:00 —— 90:00 (但不会超过100分钟),所以此处不考虑使用小时,直接以分钟计时即可。

    1. // 计时器功能
    2. timer: function () {
    3. var that = this;
    4. that.setData({
    5. millisecond: that.data.millisecond + 5
    6. })
    7. if (that.data.millisecond >= 100) {
    8. that.setData({
    9. millisecond: 0,
    10. second: that.data.second + 1
    11. })
    12. }
    13. if (that.data.second >= 60) {
    14. that.setData({
    15. second: 0,
    16. minute: that.data.minute + 1
    17. })
    18. }
    19. if (that.data.minute < 10 && that.data.second < 10) {
    20. that.setData({
    21. timecount: "0" + that.data.minute + ":" + "0" + that.data.second
    22. })
    23. }
    24. else if (that.data.minute < 10 && that.data.second > 10) {
    25. that.setData({
    26. timecount: "0" + that.data.minute + ":" + that.data.second
    27. })
    28. }
    29. else if (that.data.minute > 10 && that.data.second < 10) {
    30. that.setData({
    31. timecount: that.data.minute + ":" + "0" + that.data.second
    32. })
    33. }
    34. else {
    35. that.setData({
    36. timecount: that.data.minute + ":" + that.data.second
    37. })
    38. }
    39. },

    在实现了这些基本的函数之后,计时功能基本可以完成了,在直接测试过程中均没有发现任何问题。

    2.png

    但是虽然实现了在该页面实现计时的功能,但我却发现,当小程序被切换到后台时,计时虽然在继续但是明显出现了延迟,实际的时间经过了十分钟而小程序内计时却不足十分钟。这对于用于足球比赛计时的小程序而言无疑是毁灭性打击,但是代码运行在小程序在当前页面时则不会出现任何问题,说明这个问题应该不是代码错误导致的。

    所以问题就变成了小程序在被切到后台时,会发生哪些事件。首先从小程序的 js 文件里可以看到有一系列的生命周期函数,最有可能与这个问题出现关联的生命周期函数应该是如下两个:

    1. /**
    2. * 生命周期函数--监听页面隐藏
    3. */
    4. onHide: function () {
    5. },
    6. /**
    7. * 生命周期函数--监听页面卸载
    8. */
    9. onUnload: function () {
    10. },

    但是如果以及涉及到了onUnload()函数,那说明小程序接下来的启动已经到了冷启动阶段,到了改阶段之后启动时出现的时间将会被清零,回归到最初的 data 中初始化的变量值。所以这里既然计时还在继续,就应该是处于一个热启动的状态。

    微信小程序 - 使用 setInterval  制作计时器后台延迟问题 - 图3

    通过微信开放文档可以看到,**onHide()** 是小程序从前台进入后台时触发,也可以使用 **wx.onAppHide**绑定监听。微信内定义的前后台是,当用户点击关闭,或者按了设备 Home 键离开微信,小程序并没有直接销毁,而是进入了后台;当再次进入微信或再次打开小程序,又会从后台进入前台。所以此处onHide()函数理论上会在小程序被切换到后台时触发。因为存在延时以及被后台结束的可能,所以微信官方文档里也介绍了其实小程序是不推荐做订阅推送的功能的。

    但是因为我在此处并没有对小程序的onHide()函数进行详细的定义,所以似乎不应该会产生任何的操作。但是当小程序进入到后台,先执行页面onHide()方法再执行应用onHide()方法;而当小程序从后台进入到前台,先执行应用onShow()方法再执行页面onShow()方法。所以反复切换的过程中不仅仅是onHide()onShow()也是会被调用的函数。这种反复启动被称为热启动,根据分析,如果是热启动时小程序在后台界面并导致页面计时出现延迟的原因应该是在调用onHide()以及onShow()这两个生命周期函数。根据多次测试不同时长的后台计时,从延迟的时间相差不大来看,应该是存在一个时间基本固定的延时。从上图小程序的生命周期图片来看,onShow()onHide()在切换时会出现一个Send Data的时间,所以我认为虽然onShow()onHide()在我的文件里并没有编写任何的函数功能,但是由于存在一个Send Data的时间,所以导致了一个固定时间的延时。同时,当重新回到页面上时,也有可能会因为重新获取数据渲染页面导致出现不同程度的延时,这与网速以及手机性能都有关系。所以最佳的解决方案应该是强制将这个页面挂载在前台,且保持不息屏不随意切换页面,或者可以使用onHide()在切换到后台时记录当前时间,onShow()在切换到前台时获取当前时间并进行时间修改展示。