You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 05:35:50