主题切换
版本更新与热更新
预计阅读 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 等)的能力。
核心原则
合规边界
- ✅ 可热更新:图片、音频、动画配置、关卡数据、UI 布局 JSON
- ❌ 不能热更新:游戏可执行逻辑代码(JS/WASM)、
game.json、project.config.json - ⚠️ 灰色地带:Lua/JS 脚本作为"数据"加载后通过解释器执行 — 微信审核可能判定为规避审核
资源版本化管理
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))本章小结
- 微信小游戏更新分为代码包审核更新与资源热更新两条线。
wx.getUpdateManager负责代码包静默下载与提示重启。- 热更新只更新图片、音频、配置等数据,不能更新可执行代码。
- 配合 CI/CD 自动生成
manifest.json与差量下载是生产标配。
📝 课后练习
练习 1:热更新边界分析
题目: 列出 5 项可以热更新的资源与 3 项绝对不能热更新的内容,并说明原因。
参考答案:
- 可热更新:贴图、音效、关卡 JSON、UI 布局、Spine 数据。
- 不可热更新:JS/WASM 逻辑、
game.json、支付/广告配置。
练习 2:差量 manifest 设计
题目: 在示例 generateManifest 基础上,增加本地缓存校验,只下载 sha256 变化的文件。
参考答案思路: 读取本地 manifest.json,按 path 比对 sha256,不相同才加入下载队列。
📚 相关阅读
- CI/CD 构建与部署 — 自动生成热更新 manifest 的流水线
- 文件系统与本地存储 — 资源缓存与目录管理
- 安全与反作弊 — 防止本地 manifest 被篡改