Skip to content

版本更新与热更新

预计阅读 15 分钟
版本信息
最后更新:2026-07-09 · 适用基础库:≥ 3.9.0(最低兼容 ≥ 2.30.0) · 微信开发者工具:稳定版 1.06+
前端对比:Web 更新 vs 小游戏更新
维度Web 应用更新小游戏更新
代码更新刷新页面即可需通过 wx.getUpdateManager + 用户重启
资源更新CDN 版本号刷新自建热更新(CDN 下载 + 本地缓存)
更新粒度按文件(HMR)或全量(部署)代码包全量 + 资源包差异下载
回滚机制重新部署旧版本版本管理后台切换
更新感知用户无感(自动加载最新)用户需主动确认重启
审核要求代码更新需重新审核,资源热更新不需要

适应技巧:在 Web 端,部署即上线;在小游戏中,代码部署(上传)和用户更新(重启)是两个独立步骤。 你的 CI/CD 需要区分"代码包提审"和"资源热更新"两条独立流地址。

微信小游戏的更新机制分为两类:代码包版本更新(通过审核流程)和资源热更新(CDN 动态加载)。本章讲解两种机制的实现与最佳实践。


代码包版本更新

wx.getUpdateManager

微信提供了内置的更新管理器,用于检测和提示用户更新到最新版本:

typescript
class UpdateManager {
  static init(): void {
    const updateManager = wx.getUpdateManager()

    // 检测到新版本
    updateManager.onCheckForUpdate((res) => {
      if (res.hasUpdate) {
        console.log('发现新版本,准备下载...')
        // 可在此处静默下载,或在合适时机提示用户
      }
    })

    // 新版本下载完成
    updateManager.onUpdateReady(() => {
      wx.showModal({
        title: '更新提示',
        content: '新版本已就绪,是否重启应用?',
        success: (res) => {
          if (res.confirm) {
            // 新版已下载完成,调用 applyUpdate 立即重启
            updateManager.applyUpdate()
          }
          // 用户选择稍后重启,下次 onShow 时再次提醒
        },
      })
    })

    // 新版本下载失败
    updateManager.onUpdateFailed(() => {
      console.warn('新版本下载失败')
      // 提示用户检查网络,或在下次启动时自动重试
    })
  }
}

强制更新策略

对于有重大 Bug 修复的版本,可使用强制更新:

typescript
class ForceUpdateStrategy {
  private static FORCE_UPDATE_THRESHOLD = 3 // 提示 3 次后强制重启

  private static promptCount = 0

  static checkAndPrompt(): void {
    const updateManager = wx.getUpdateManager()

    updateManager.onUpdateReady(() => {
      if (this.promptCount >= this.FORCE_UPDATE_THRESHOLD) {
        // 超过阈值,强制重启
        updateManager.applyUpdate()
        return
      }

      this.promptCount++

      wx.showModal({
        title: '更新提示',
        content:
          this.promptCount < 3
            ? `发现新版本,建议立即更新(${3 - this.promptCount} 次后自动更新)`
            : '即将自动重启以应用更新...',
        showCancel: this.promptCount < 3,
        confirmText: '立即更新',
        cancelText: '稍后再说',
        success: (res) => {
          if (res.confirm) {
            updateManager.applyUpdate()
          }
        },
      })
    })
  }
}

资源热更新

资源热更新是指不经过微信审核,直接从 CDN 加载游戏资源(图片、音频、配置 JSON 等)的能力。

核心原则

资源版本化管理

typescript
class HotUpdateManager {
  private static REMOTE_MANIFEST_URL = 'https://cdn.example.com/game/manifest.json'

  /** 远程资源清单(由 CI/CD 生成) */
  static async checkForUpdates(): Promise<boolean> {
    const localVersion = this.getLocalVersion()
    const remoteManifest = await this.fetchManifest()

    if (remoteManifest.version === localVersion) {
      console.log('资源已是最新')
      return false
    }

    // 下载差异资源
    await this.downloadDiff(remoteManifest.files)
    this.saveLocalVersion(remoteManifest.version)
    return true
  }

  /** 获取本地缓存版本 */
  private static getLocalVersion(): string {
    try {
      return wx.getStorageSync('asset_version') || '0'
    } catch {
      return '0'
    }
  }

  /** 保存更新后的版本号 */
  private static saveLocalVersion(version: string): void {
    wx.setStorageSync('asset_version', version)
  }

  /** 拉取远程资源清单 */
  private static async fetchManifest(): Promise<{
    version: string
    files: Array<{ path: string; url: string; sha256: string }>
  }> {
    return new Promise((resolve, reject) => {
      wx.request({
        url: `${this.REMOTE_MANIFEST_URL}?t=${Date.now()}`,
        success: (res) => {
          if (res.statusCode === 200) resolve(res.data as any)
          else reject(new Error(`HTTP ${res.statusCode}`))
        },
        fail: reject,
      })
    })
  }

  /** 下载差异文件到本地缓存 */
  private static async downloadDiff(
    files: Array<{ path: string; url: string; sha256: string }>
  ): Promise<void> {
    const fs = wx.getFileSystemManager()
    const total = files.length

    for (let i = 0; i < files.length; i++) {
      const file = files[i]
      const localPath = `${wx.env.USER_DATA_PATH}/assets/${file.path}`

      // 确保目录存在
      const dir = localPath.substring(0, localPath.lastIndexOf('/'))
      try {
        fs.accessSync(dir)
      } catch {
        fs.mkdirSync(dir, true)
      }

      // 下载文件
      await new Promise<void>((resolve, reject) => {
        wx.downloadFile({
          url: file.url,
          success: (res) => {
            if (res.statusCode === 200) {
              fs.saveFile({
                tempFilePath: res.tempFilePath,
                filePath: localPath,
                success: resolve,
                fail: reject,
              })
            } else {
              reject(new Error(`下载失败: ${file.path} (HTTP ${res.statusCode})`))
            }
          },
          fail: reject,
        })
      })

      // 进度回调
      console.log(`资源更新进度: ${i + 1}/${total}`)
    }
  }

  /** 获取资源的本地路径 */
  static resolveLocalPath(remoteUrl: string): string {
    // 如果本地有缓存,返回本地路径;否则返回远程 URL
    const cachePath = `${wx.env.USER_DATA_PATH}/assets/${this.pathFromUrl(remoteUrl)}`
    try {
      wx.getFileSystemManager().accessSync(cachePath)
      return cachePath
    } catch {
      return remoteUrl // 回退到远程加载
    }
  }

  private static pathFromUrl(url: string): string {
    const parsed = url.replace(/^https?:\/\/[^/]+/, '')
    return parsed.startsWith('/') ? parsed.slice(1) : parsed
  }
}

更新时机选择

typescript
// 在启动 loading 阶段检查更新
async function bootstrap(): Promise<void> {
  showLoadingScreen()

  const hasUpdate = await HotUpdateManager.checkForUpdates()
  if (hasUpdate) {
    console.log('资源已更新,准备重新进入游戏...')
    // 可选择提示用户或直接重启
    wx.showToast({ title: '资源更新完成', icon: 'success' })
  }

  hideLoadingScreen()
  enterGame()
}

CI/CD 自动生成 Manifest

在发布流程中,构建脚本自动生成 manifest.json

typescript
// 构建工具脚本:generateManifest.ts
import * as fs from 'fs'
import * as path from 'path'
import * as crypto from 'crypto'

function generateManifest(assetsDir: string, cdnBase: string) {
  const files: Array<{ path: string; url: string; sha256: string }> = []

  function walk(dir: string, relativePath = '') {
    const entries = fs.readdirSync(dir, { withFileTypes: true })
    for (const entry of entries) {
      const fullPath = path.join(dir, entry.name)
      const relPath = path.join(relativePath, entry.name)
      if (entry.isDirectory()) {
        walk(fullPath, relPath)
      } else {
        const content = fs.readFileSync(fullPath)
        files.push({
          path: relPath.replace(/\\/g, '/'),
          url: `${cdnBase}/${relPath.replace(/\\/g, '/')}`,
          // 使用 SHA-256 替代 MD5(更强的完整性校验)
          sha256: crypto.createHash('sha256').update(content).digest('hex'),
        })
      }
    }
  }

  walk(assetsDir)

  return {
    version: String(Date.now()),
    generatedAt: new Date().toISOString(),
    files,
  }
}

// 输出到 dist 目录
const manifest = generateManifest('./dist/assets', 'https://cdn.example.com/game')
fs.writeFileSync('./dist/manifest.json', JSON.stringify(manifest, null, 2))

本章小结

  1. 微信小游戏更新分为代码包审核更新与资源热更新两条线。
  2. wx.getUpdateManager 负责代码包静默下载与提示重启。
  3. 热更新只更新图片、音频、配置等数据,不能更新可执行代码。
  4. 配合 CI/CD 自动生成 manifest.json 与差量下载是生产标配。

📝 课后练习

练习 1:热更新边界分析

题目: 列出 5 项可以热更新的资源与 3 项绝对不能热更新的内容,并说明原因。

参考答案:

  • 可热更新:贴图、音效、关卡 JSON、UI 布局、Spine 数据。
  • 不可热更新:JS/WASM 逻辑、game.json、支付/广告配置。
练习 2:差量 manifest 设计

题目: 在示例 generateManifest 基础上,增加本地缓存校验,只下载 sha256 变化的文件。

参考答案思路: 读取本地 manifest.json,按 path 比对 sha256,不相同才加入下载队列。

📚 相关阅读

用心学习,持续实践