如何在Flutter应用中实现类游戏的空中更新(非in_app_update包)
Flutter 实现空中(On-the-Air)热更新的核心工作流程
你需要的是动态更新Flutter代码/资源的热更新方案,和pub.dev的in_app_update(调用应用商店整包更新)完全不同,这类功能的核心逻辑类似手游的小版本更新,以下是完整的工作流程和实现要点:
一、整体工作流程
- 应用启动时,向自定义更新服务器发送请求,携带两个关键标识:当前热更新版本号、原生应用版本号(APK/IPA的版本)
- 服务器对比版本信息,返回更新状态:无需更新、可选更新、强制更新,同时返回更新包地址、哈希校验值
- 客户端下载更新包(增量/全量Dart代码快照或资源压缩包)
- 校验更新包完整性(用哈希值比对,防止篡改)
- 替换本地旧的代码/资源文件,同时备份旧版本用于回滚
- 提示用户重启应用,重启后加载新的代码/资源
二、核心模块实现
1. 更新服务器侧
- 版本管理:维护热更新版本与原生版本的对应关系,确保热更新包只兼容指定原生版本(避免跨原生版本更新导致崩溃)
- 更新包生成:每次发布热更新时,编译需要更新的Dart代码为快照,或打包资源文件,计算哈希值后上传到服务器
- 版本校验接口:接收客户端的版本信息,返回是否需要更新、更新包地址、哈希值、更新类型(强制/可选)
2. 客户端侧关键步骤
(1)启动时检查更新
在应用初始化阶段发起版本检查,优先于主页面加载:
void main() async { WidgetsFlutterBinding.ensureInitialized(); await _checkHotUpdate(); runApp(const MyApp()); } Future<void> _checkHotUpdate() async { final prefs = await SharedPreferences.getInstance(); // 读取本地存储的当前热更新版本,默认与原生版本一致 final currentHotVersion = prefs.getString('hot_version') ?? '1.0.0'; // 读取原生版本(可通过platform_channel获取) final nativeVersion = await _getNativeVersion(); final response = await http.post( Uri.parse('/api/check-update'), body: {'hot_version': currentHotVersion, 'native_version': nativeVersion}, ); if (response.statusCode == 200) { final updateData = jsonDecode(response.body); if (updateData['need_update'] == true) { await _downloadAndInstallUpdate(updateData['package_url'], updateData['hash'], updateData['new_hot_version']); } } }
(2)下载与校验更新包
下载时处理进度,完成后校验哈希值确保包的完整性:
Future<void> _downloadAndInstallUpdate(String url, String expectedHash, String newVersion) async { final tempDir = await getTemporaryDirectory(); final updateFile = File('${tempDir.path}/hot_update.zip'); // 下载更新包 final httpResponse = await http.get(Uri.parse(url)); await updateFile.writeAsBytes(httpResponse.bodyBytes); // 校验哈希(示例用SHA256,比MD5更安全) final fileBytes = await updateFile.readAsBytes(); final fileHash = sha256.convert(fileBytes).toString(); if (fileHash != expectedHash) { await updateFile.delete(); throw Exception('更新包校验失败,已取消更新'); } // 校验通过,安装更新 await _installUpdate(updateFile, newVersion); }
(3)替换资源/代码并备份
将更新包解压到自定义存储目录,同时备份旧版本用于回滚:
Future<void> _installUpdate(File updateFile, String newVersion) async { final appDocDir = await getApplicationDocumentsDirectory(); final targetAssetsDir = Directory('${appDocDir.path}/flutter_hot_assets'); final backupDir = Directory('${appDocDir.path}/flutter_hot_assets_backup'); // 备份旧资源 if (await targetAssetsDir.exists()) { await targetAssetsDir.rename(backupDir.path); } // 解压更新包到目标目录 await ZipFile.extractToDirectory(zipFile: updateFile, destination: targetAssetsDir); // 更新本地热更新版本号 final prefs = await SharedPreferences.getInstance(); await prefs.setString('hot_version', newVersion); // 提示用户重启 if (navigatorKey.currentContext != null) { showDialog( context: navigatorKey.currentContext!, barrierDismissible: false, builder: (ctx) => AlertDialog( title: const Text('更新完成'), content: const Text('请重启应用以应用最新内容'), actions: [ TextButton( onPressed: () => FlutterRestart.restartApp(), // 使用第三方包实现重启 child: const Text('立即重启'), ), ], ), ); } }
(4)重启后加载新资源
需要修改原生层代码,让Flutter优先加载自定义目录下的资源,而非APK/IPA内置资源:
- Android:在
MainActivity的configureFlutterEngine方法中,修改资源加载路径 - iOS:在
AppDelegate的application:didFinishLaunchingWithOptions:方法中,配置FlutterEngine的资源路径
三、关键注意事项
- 平台政策限制:iOS App Store严格禁止动态加载代码,这类热更新方案仅适用于企业签名或非上架应用;Android Google Play允许有限制的热更新,但需遵守政策。
- 版本兼容性:热更新包必须与当前原生应用版本完全兼容,否则会引发崩溃,服务器需严格校验原生版本。
- 回滚机制:必须保留旧版本备份,若更新失败(如解压错误、加载失败),需自动恢复到备份的旧版本。
- 安全性:更新包必须通过HTTPS传输,哈希校验不可省略,防止恶意代码注入。
内容的提问来源于stack exchange,提问作者Ayyaz Shair
相关产品推荐
相关产品推荐

