# Flutter Android APK 在线升级完整方案 > 支持 Google Play 和自建服务器双渠道的 Flutter Android 应用升级解决方案 ## 目录 - [一、方案概述](#一方案概述) - [二、技术栈与依赖](#二技术栈与依赖) - [三、架构设计](#三架构设计) - [四、Google Play 应用内更新](#四google-play-应用内更新) - [五、自建服务器 APK 升级](#五自建服务器-apk-升级) - [六、统一升级服务](#六统一升级服务) - [七、后端接口设计](#七后端接口设计) - [八、Android 原生配置](#八android-原生配置) - [九、安全性考虑](#九安全性考虑) - [十、实施步骤](#十实施步骤) --- ## 一、方案概述 ### 1.1 升级渠道对比 | 渠道 | 适用场景 | 升级体验 | 审核要求 | |------|---------|---------|---------| | Google Play | 海外市场、正规渠道 | 应用内更新,体验流畅 | 需要审核 | | 自建服务器 | 国内市场、企业内部 | 下载 APK 安装 | 无需审核 | ### 1.2 核心特性 - 支持 Google Play 应用内更新(灵活更新、强制更新) - 支持自建服务器 APK 下载安装 - 使用应用专属目录,无需外部存储权限 - SHA-256 文件完整性校验 - 支持应用市场来源检测 - 渠道构建层面完全隔离 - 强制 HTTPS 下载 --- ## 二、技术栈与依赖 ### 2.1 Flutter 依赖 ```yaml dependencies: # 版本信息 package_info_plus: ^8.0.0 # HTTP 请求与下载 dio: ^5.4.0 # Google Play 应用内更新 in_app_update: ^4.2.2 # 权限管理 permission_handler: ^11.0.0 # 路径获取 path_provider: ^2.1.0 # SHA-256 校验 crypto: ^3.0.3 # URL 启动 url_launcher: ^6.2.0 ``` ### 2.2 Android 依赖 Google Play Core Library 会由 `in_app_update` 插件自动添加。 --- ## 三、架构设计 ### 3.1 目录结构 ``` lib/ └── core/ └── updater/ ├── models/ │ ├── version_info.dart # 版本信息模型 │ └── update_config.dart # 更新配置 ├── channels/ │ ├── google_play_updater.dart # Google Play 更新 │ └── self_hosted_updater.dart # 自建服务器更新 ├── version_checker.dart # 版本检测 ├── download_manager.dart # 下载管理 ├── apk_installer.dart # APK 安装器 ├── app_market_detector.dart # 应用市场检测 └── update_service.dart # 升级服务统一入口 ``` --- ## 四、Google Play 应用内更新 ### 4.1 Google Play 更新器实现 **文件: `lib/core/updater/channels/google_play_updater.dart`** ```dart import 'package:in_app_update/in_app_update.dart'; enum GooglePlayUpdateType { flexible, // 灵活更新:后台下载,可继续使用 immediate, // 强制更新:阻塞式,必须更新 } class GooglePlayUpdater { /// 检查是否有更新可用 static Future checkForUpdate() async { try { final updateInfo = await InAppUpdate.checkForUpdate(); return updateInfo.updateAvailability == UpdateAvailability.updateAvailable; } catch (e) { print('Check update failed: $e'); return false; } } /// 灵活更新 /// 用户可以在后台下载,继续使用应用 static Future performFlexibleUpdate() async { try { final updateInfo = await InAppUpdate.checkForUpdate(); if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) { if (updateInfo.flexibleUpdateAllowed) { await InAppUpdate.startFlexibleUpdate(); InAppUpdate.completeFlexibleUpdate().then((_) { print('Update completed, app will restart'); }).catchError((e) { print('Update failed: $e'); }); } else { print('Flexible update not allowed'); } } } catch (e) { print('Flexible update error: $e'); } } /// 强制更新 /// 阻塞式更新,用户必须更新才能继续使用 static Future performImmediateUpdate() async { try { final updateInfo = await InAppUpdate.checkForUpdate(); if (updateInfo.updateAvailability == UpdateAvailability.updateAvailable) { if (updateInfo.immediateUpdateAllowed) { await InAppUpdate.performImmediateUpdate(); } else { print('Immediate update not allowed'); } } } catch (e) { print('Immediate update error: $e'); } } /// 智能更新策略 /// 根据版本差异决定更新方式 static Future smartUpdate({ required int currentVersion, required int latestVersion, }) async { final versionDiff = latestVersion - currentVersion; if (versionDiff >= 10) { // 版本差异大,强制更新 await performImmediateUpdate(); } else { // 版本差异小,灵活更新 await performFlexibleUpdate(); } } } ``` ### 4.2 使用示例 ```dart class _MyAppState extends State { @override void initState() { super.initState(); _checkGooglePlayUpdate(); } Future _checkGooglePlayUpdate() async { await Future.delayed(const Duration(seconds: 3)); final hasUpdate = await GooglePlayUpdater.checkForUpdate(); if (hasUpdate) { _showUpdateDialog(); } } void _showUpdateDialog() { showDialog( context: context, barrierDismissible: false, builder: (context) => AlertDialog( title: const Text('发现新版本'), content: const Text('有新版本可用,是否立即更新?'), actions: [ TextButton( onPressed: () { Navigator.pop(context); GooglePlayUpdater.performFlexibleUpdate(); }, child: const Text('更新'), ), TextButton( onPressed: () => Navigator.pop(context), child: const Text('稍后'), ), ], ), ); } @override Widget build(BuildContext context) { return MaterialApp(/* ... */); } } ``` --- ## 五、自建服务器 APK 升级 ### 5.1 版本信息模型 **文件: `lib/core/updater/models/version_info.dart`** ```dart import 'package:json_annotation/json_annotation.dart'; part 'version_info.g.dart'; @JsonSerializable() class VersionInfo { final String version; // 版本号: "1.2.0" final int versionCode; // 版本代码: 120 final String downloadUrl; // APK 下载地址 final int fileSize; // 文件大小(字节) final String fileSizeFriendly; // 友好显示: "25.6 MB" final String sha256; // SHA-256 校验 final bool forceUpdate; // 是否强制更新 final String? updateLog; // 更新日志 final DateTime releaseDate; // 发布时间 VersionInfo({ required this.version, required this.versionCode, required this.downloadUrl, required this.fileSize, required this.fileSizeFriendly, required this.sha256, this.forceUpdate = false, this.updateLog, required this.releaseDate, }); factory VersionInfo.fromJson(Map json) => _$VersionInfoFromJson(json); Map toJson() => _$VersionInfoToJson(this); } ``` ### 5.2 版本检测器 **文件: `lib/core/updater/version_checker.dart`** ```dart import 'package:package_info_plus/package_info_plus.dart'; import 'package:dio/dio.dart'; import 'models/version_info.dart'; class VersionChecker { final String apiBaseUrl; final Dio _dio; VersionChecker({required this.apiBaseUrl}) : _dio = Dio(BaseOptions( baseUrl: apiBaseUrl, connectTimeout: const Duration(seconds: 10), )); /// 获取当前版本信息 Future getCurrentVersion() async { return await PackageInfo.fromPlatform(); } /// 从服务器获取最新版本信息 Future fetchLatestVersion() async { try { final response = await _dio.get('/api/app/version/check'); if (response.statusCode == 200) { return VersionInfo.fromJson(response.data); } return null; } catch (e) { print('Fetch version failed: $e'); return null; } } /// 检查是否有新版本 Future checkForUpdate() async { try { final currentInfo = await getCurrentVersion(); final latestInfo = await fetchLatestVersion(); if (latestInfo == null) return null; final currentCode = int.parse(currentInfo.buildNumber); if (latestInfo.versionCode > currentCode) { return latestInfo; } return null; } catch (e) { print('Check update failed: $e'); return null; } } /// 是否需要强制更新 Future needForceUpdate() async { final latestInfo = await checkForUpdate(); return latestInfo?.forceUpdate ?? false; } } ``` ### 5.3 下载管理器 **文件: `lib/core/updater/download_manager.dart`** ```dart import 'dart:io'; import 'package:dio/dio.dart'; import 'package:path_provider/path_provider.dart'; import 'package:crypto/crypto.dart'; import 'dart:convert'; class DownloadManager { final Dio _dio = Dio(); CancelToken? _cancelToken; /// 下载 APK 文件 /// [url] 下载地址(必须是 HTTPS) /// [sha256Expected] SHA-256 校验值 /// [onProgress] 下载进度回调 (已下载字节, 总字节) Future downloadApk({ required String url, required String sha256Expected, Function(int received, int total)? onProgress, }) async { try { // 强制 HTTPS if (!url.startsWith('https://')) { print('Download URL must use HTTPS'); return null; } _cancelToken = CancelToken(); // 使用应用专属目录(无需额外权限) final dir = await getApplicationDocumentsDirectory(); final savePath = '${dir.path}/app_update.apk'; final file = File(savePath); if (await file.exists()) { await file.delete(); } print('Downloading APK to: $savePath'); await _dio.download( url, savePath, cancelToken: _cancelToken, onReceiveProgress: (received, total) { if (total != -1) { final progress = (received / total * 100).toStringAsFixed(0); print('Download progress: $progress%'); onProgress?.call(received, total); } }, options: Options( receiveTimeout: const Duration(minutes: 10), sendTimeout: const Duration(minutes: 10), ), ); print('Download completed'); // 校验 SHA-256 final isValid = await _verifySha256(file, sha256Expected); if (!isValid) { print('SHA-256 verification failed'); await file.delete(); return null; } print('SHA-256 verified'); return file; } catch (e) { print('Download failed: $e'); return null; } } /// 取消下载 void cancelDownload() { _cancelToken?.cancel('User cancelled download'); } /// 校验文件 SHA-256 Future _verifySha256(File file, String expectedSha256) async { try { final bytes = await file.readAsBytes(); final digest = sha256.convert(bytes); final actualSha256 = digest.toString(); print('Expected SHA-256: $expectedSha256'); print('Actual SHA-256: $actualSha256'); return actualSha256.toLowerCase() == expectedSha256.toLowerCase(); } catch (e) { print('SHA-256 verification error: $e'); return false; } } } ``` ### 5.4 APK 安装器 **文件: `lib/core/updater/apk_installer.dart`** ```dart import 'dart:io'; import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; class ApkInstaller { static const MethodChannel _channel = MethodChannel('com.yourapp/apk_installer'); /// 安装 APK static Future installApk(File apkFile) async { try { if (!await apkFile.exists()) { print('APK file not found'); return false; } // Android 8.0+ 需要请求安装权限 if (Platform.isAndroid) { final hasPermission = await _requestInstallPermission(); if (!hasPermission) { print('Install permission denied'); return false; } } print('Installing APK: ${apkFile.path}'); final result = await _channel.invokeMethod('installApk', { 'apkPath': apkFile.path, }); print('Installation triggered: $result'); return true; } catch (e) { print('Install failed: $e'); return false; } } /// 请求安装权限(Android 8.0+) static Future _requestInstallPermission() async { if (await Permission.requestInstallPackages.isGranted) { return true; } final status = await Permission.requestInstallPackages.request(); return status.isGranted; } } ``` ### 5.5 应用市场检测器 **文件: `lib/core/updater/app_market_detector.dart`** ```dart import 'dart:io'; import 'package:flutter/services.dart'; import 'package:url_launcher/url_launcher.dart'; class AppMarketDetector { static const MethodChannel _channel = MethodChannel('com.yourapp/app_market'); /// 获取安装来源 static Future getInstallerPackageName() async { if (!Platform.isAndroid) return null; try { final installer = await _channel.invokeMethod('getInstallerPackageName'); return installer; } catch (e) { print('Get installer failed: $e'); return null; } } /// 判断是否来自应用市场 static Future isFromAppMarket() async { final installer = await getInstallerPackageName(); if (installer == null || installer.isEmpty) { return false; // 直接安装 } // 常见应用市场包名 const marketPackages = [ 'com.huawei.appmarket', 'com.xiaomi.market', 'com.oppo.market', 'com.bbk.appstore', 'com.tencent.android.qqdownloader', 'com.qihoo.appstore', 'com.baidu.appsearch', 'com.wandoujia.phoenix2', 'com.dragon.android.pandaspace', 'com.sec.android.app.samsungapps', ]; return marketPackages.contains(installer); } /// 打开应用市场详情页 static Future openAppMarketDetail(String packageName) async { final marketUri = Uri.parse('market://details?id=$packageName'); if (await canLaunchUrl(marketUri)) { await launchUrl(marketUri, mode: LaunchMode.externalApplication); } else { final webUri = Uri.parse('https://play.google.com/store/apps/details?id=$packageName'); await launchUrl(webUri, mode: LaunchMode.externalApplication); } } } ``` ### 5.6 自建服务器更新器 **文件: `lib/core/updater/channels/self_hosted_updater.dart`** ```dart import 'dart:io'; import 'package:flutter/material.dart'; import '../version_checker.dart'; import '../download_manager.dart'; import '../apk_installer.dart'; import '../app_market_detector.dart'; import '../models/version_info.dart'; class SelfHostedUpdater { final VersionChecker versionChecker; final DownloadManager downloadManager; SelfHostedUpdater({ required String apiBaseUrl, }) : versionChecker = VersionChecker(apiBaseUrl: apiBaseUrl), downloadManager = DownloadManager(); /// 检查并提示更新 Future checkAndPromptUpdate(BuildContext context) async { final versionInfo = await versionChecker.checkForUpdate(); if (versionInfo == null) { print('Already latest version'); return; } if (!context.mounted) return; // 检测安装来源 final isFromMarket = await AppMarketDetector.isFromAppMarket(); if (isFromMarket) { _showMarketUpdateDialog(context, versionInfo); } else { _showSelfHostedUpdateDialog(context, versionInfo); } } /// 应用市场更新提示 void _showMarketUpdateDialog(BuildContext context, VersionInfo versionInfo) { showDialog( context: context, barrierDismissible: !versionInfo.forceUpdate, builder: (context) => WillPopScope( onWillPop: () async => !versionInfo.forceUpdate, child: AlertDialog( title: Text(versionInfo.forceUpdate ? '发现重要更新' : '发现新版本'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('最新版本: ${versionInfo.version}'), if (versionInfo.updateLog != null) ...[ const SizedBox(height: 16), const Text('更新内容:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text(versionInfo.updateLog!), ], const SizedBox(height: 16), const Text( '检测到您的应用来自应用市场,建议前往应用市场更新以获得最佳体验。', style: TextStyle(fontSize: 12, color: Colors.grey), ), ], ), ), actions: [ if (!versionInfo.forceUpdate) TextButton( onPressed: () => Navigator.pop(context), child: const Text('稍后'), ), ElevatedButton( onPressed: () async { Navigator.pop(context); final packageInfo = await versionChecker.getCurrentVersion(); await AppMarketDetector.openAppMarketDetail(packageInfo.packageName); }, child: const Text('前往应用市场'), ), ], ), ), ); } /// 自建更新对话框 void _showSelfHostedUpdateDialog(BuildContext context, VersionInfo versionInfo) { showDialog( context: context, barrierDismissible: !versionInfo.forceUpdate, builder: (context) => WillPopScope( onWillPop: () async => !versionInfo.forceUpdate, child: AlertDialog( title: Text(versionInfo.forceUpdate ? '发现重要更新' : '发现新版本'), content: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text('最新版本: ${versionInfo.version}'), const SizedBox(height: 8), Text('文件大小: ${versionInfo.fileSizeFriendly}'), if (versionInfo.updateLog != null) ...[ const SizedBox(height: 16), const Text('更新内容:', style: TextStyle(fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text(versionInfo.updateLog!), ], ], ), ), actions: [ if (!versionInfo.forceUpdate) TextButton( onPressed: () => Navigator.pop(context), child: const Text('稍后'), ), ElevatedButton( onPressed: () { Navigator.pop(context); _startUpdate(context, versionInfo); }, child: const Text('立即更新'), ), ], ), ), ); } /// 开始下载并安装 Future _startUpdate(BuildContext context, VersionInfo versionInfo) async { if (!context.mounted) return; showDialog( context: context, barrierDismissible: false, builder: (context) => _DownloadProgressDialog( versionInfo: versionInfo, downloadManager: downloadManager, ), ); } } /// 下载进度对话框 class _DownloadProgressDialog extends StatefulWidget { final VersionInfo versionInfo; final DownloadManager downloadManager; const _DownloadProgressDialog({ required this.versionInfo, required this.downloadManager, }); @override State<_DownloadProgressDialog> createState() => _DownloadProgressDialogState(); } class _DownloadProgressDialogState extends State<_DownloadProgressDialog> { double _progress = 0.0; String _statusText = '准备下载...'; bool _isDownloading = true; @override void initState() { super.initState(); _startDownload(); } Future _startDownload() async { setState(() { _statusText = '正在下载...'; }); final apkFile = await widget.downloadManager.downloadApk( url: widget.versionInfo.downloadUrl, sha256Expected: widget.versionInfo.sha256, onProgress: (received, total) { setState(() { _progress = received / total; final receivedMB = (received / 1024 / 1024).toStringAsFixed(1); final totalMB = (total / 1024 / 1024).toStringAsFixed(1); _statusText = '正在下载... $receivedMB MB / $totalMB MB'; }); }, ); if (apkFile == null) { setState(() { _statusText = '下载失败'; _isDownloading = false; }); return; } setState(() { _statusText = '下载完成,准备安装...'; }); await Future.delayed(const Duration(milliseconds: 500)); final installed = await ApkInstaller.installApk(apkFile); if (!mounted) return; if (installed) { Navigator.pop(context); } else { setState(() { _statusText = '安装失败'; _isDownloading = false; }); } } @override Widget build(BuildContext context) { return WillPopScope( onWillPop: () async => !_isDownloading, child: AlertDialog( title: const Text('正在更新'), content: Column( mainAxisSize: MainAxisSize.min, children: [ LinearProgressIndicator(value: _progress), const SizedBox(height: 16), Text(_statusText), ], ), actions: [ if (!_isDownloading) TextButton( onPressed: () { widget.downloadManager.cancelDownload(); Navigator.pop(context); }, child: const Text('关闭'), ), ], ), ); } } ``` --- ## 六、统一升级服务 ### 6.1 升级服务入口 **文件: `lib/core/updater/update_service.dart`** ```dart import 'package:flutter/material.dart'; import 'channels/google_play_updater.dart'; import 'channels/self_hosted_updater.dart'; enum UpdateChannel { googlePlay, selfHosted, } class UpdateService { static UpdateService? _instance; UpdateService._(); factory UpdateService() { _instance ??= UpdateService._(); return _instance!; } late UpdateChannel _channel; late String _apiBaseUrl; SelfHostedUpdater? _selfHostedUpdater; /// 初始化 void initialize({ required UpdateChannel channel, String? apiBaseUrl, }) { _channel = channel; if (channel == UpdateChannel.selfHosted) { if (apiBaseUrl == null) { throw ArgumentError('apiBaseUrl is required for self-hosted channel'); } _apiBaseUrl = apiBaseUrl; _selfHostedUpdater = SelfHostedUpdater(apiBaseUrl: apiBaseUrl); } print('UpdateService initialized with channel: $_channel'); } /// 检查更新 Future checkForUpdate(BuildContext context) async { switch (_channel) { case UpdateChannel.googlePlay: await _checkGooglePlayUpdate(context); break; case UpdateChannel.selfHosted: await _selfHostedUpdater!.checkAndPromptUpdate(context); break; } } Future _checkGooglePlayUpdate(BuildContext context) async { final hasUpdate = await GooglePlayUpdater.checkForUpdate(); if (!hasUpdate) { print('No update available'); return; } if (!context.mounted) return; showDialog( context: context, builder: (context) => AlertDialog( title: const Text('发现新版本'), content: const Text('有新版本可用,是否立即更新?'), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('稍后'), ), ElevatedButton( onPressed: () { Navigator.pop(context); GooglePlayUpdater.performFlexibleUpdate(); }, child: const Text('更新'), ), ], ), ); } /// 手动检查更新 Future manualCheckUpdate(BuildContext context) async { showDialog( context: context, barrierDismissible: false, builder: (context) => const Center( child: CircularProgressIndicator(), ), ); await Future.delayed(const Duration(seconds: 1)); if (!context.mounted) return; Navigator.pop(context); await checkForUpdate(context); } } ``` ### 6.2 使用示例 **在 main.dart 中初始化:** ```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); UpdateService().initialize( channel: UpdateChannel.selfHosted, apiBaseUrl: 'https://your-api.com', ); runApp(const MyApp()); } ``` **在 App 启动时检查更新:** ```dart class _MyAppState extends State { @override void initState() { super.initState(); _checkUpdate(); } Future _checkUpdate() async { await Future.delayed(const Duration(seconds: 3)); if (!mounted) return; await UpdateService().checkForUpdate(context); } @override Widget build(BuildContext context) { return MaterialApp(/* ... */); } } ``` --- ## 七、后端接口设计 ### 7.1 版本检测接口 ``` GET /api/app/version/check Query Parameters: - platform: android | ios - current_version: 1.1.0 - current_version_code: 110 Response: { "version": "1.2.0", "versionCode": 120, "downloadUrl": "https://your-cdn.com/app-release-v1.2.0.apk", "fileSize": 26843545, "fileSizeFriendly": "25.6 MB", "sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", "forceUpdate": false, "updateLog": "1. 修复若干已知问题\n2. 优化用户体验", "releaseDate": "2024-01-15T10:00:00Z" } ``` ### 7.2 后端实现示例 Node.js + Express: ```javascript app.get('/api/app/version/check', async (req, res) => { const { platform, current_version_code } = req.query; const latestVersion = await db.getLatestVersion(platform); if (parseInt(current_version_code) >= latestVersion.versionCode) { return res.json({ needUpdate: false }); } res.json({ needUpdate: true, version: latestVersion.version, versionCode: latestVersion.versionCode, downloadUrl: `https://cdn.example.com/${latestVersion.apkFile}`, fileSize: latestVersion.fileSize, fileSizeFriendly: formatFileSize(latestVersion.fileSize), sha256: latestVersion.sha256, forceUpdate: latestVersion.forceUpdate, updateLog: latestVersion.updateLog, releaseDate: latestVersion.releaseDate, }); }); ``` ### 7.3 数据库表设计 ```sql CREATE TABLE app_versions ( id BIGSERIAL PRIMARY KEY, platform VARCHAR(10) NOT NULL, version VARCHAR(50) NOT NULL, version_code INTEGER NOT NULL, apk_file VARCHAR(255), download_url TEXT NOT NULL, file_size BIGINT NOT NULL, sha256 VARCHAR(64) NOT NULL, force_update BOOLEAN DEFAULT FALSE, update_log TEXT, release_date TIMESTAMP NOT NULL, is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMP DEFAULT NOW(), INDEX idx_platform (platform), INDEX idx_version_code (version_code), INDEX idx_active (is_active) ); ``` --- ## 八、Android 原生配置 ### 8.1 MainActivity 实现 **文件: `android/app/src/main/kotlin/com/yourapp/MainActivity.kt`** ```kotlin package com.yourapp import android.content.Intent import android.net.Uri import android.os.Build import androidx.core.content.FileProvider import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodChannel import java.io.File class MainActivity: FlutterActivity() { private val INSTALLER_CHANNEL = "com.yourapp/apk_installer" private val MARKET_CHANNEL = "com.yourapp/app_market" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) // APK 安装器 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, INSTALLER_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "installApk" -> { val apkPath = call.argument("apkPath") if (apkPath != null) { installApk(apkPath) result.success(true) } else { result.error("INVALID_PATH", "APK path is null", null) } } else -> result.notImplemented() } } // 应用市场检测 MethodChannel(flutterEngine.dartExecutor.binaryMessenger, MARKET_CHANNEL) .setMethodCallHandler { call, result -> when (call.method) { "getInstallerPackageName" -> { try { val installer = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { packageManager.getInstallSourceInfo(packageName).installingPackageName } else { @Suppress("DEPRECATION") packageManager.getInstallerPackageName(packageName) } result.success(installer) } catch (e: Exception) { result.success(null) } } else -> result.notImplemented() } } } private fun installApk(apkPath: String) { val apkFile = File(apkPath) if (!apkFile.exists()) return val intent = Intent(Intent.ACTION_VIEW) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK val apkUri: Uri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION FileProvider.getUriForFile( this, "${applicationContext.packageName}.fileprovider", apkFile ) } else { Uri.fromFile(apkFile) } intent.setDataAndType(apkUri, "application/vnd.android.package-archive") startActivity(intent) } } ``` ### 8.2 Gradle Flavor 配置 **文件: `android/app/build.gradle`** ```gradle android { // 其他配置... flavorDimensions "channel" productFlavors { googleplay { dimension "channel" applicationIdSuffix ".googleplay" versionNameSuffix "-gp" } china { dimension "channel" versionNameSuffix "-china" } } } ``` ### 8.3 Google Play 版本 Manifest **文件: `android/app/src/googleplay/AndroidManifest.xml`** ```xml ``` ### 8.4 自建渠道 Manifest **文件: `android/app/src/china/AndroidManifest.xml`** ```xml ``` ### 8.5 FileProvider 路径配置 **文件: `android/app/src/main/res/xml/file_paths.xml`** ```xml ``` ### 8.6 构建命令 ```bash # Google Play 版本 flutter build apk --flavor googleplay --dart-define=CHANNEL=googleplay # 国内版本 flutter build apk --flavor china --dart-define=CHANNEL=china ``` --- ## 九、安全性考虑 ### 9.1 HTTPS 强制 所有下载链接必须使用 HTTPS 协议: ```dart if (!downloadUrl.startsWith('https://')) { throw Exception('Download URL must use HTTPS'); } ``` ### 9.2 SHA-256 完整性校验 下载完成后必须校验文件完整性: ```dart final isValid = await _verifySha256(apkFile, expectedSha256); if (!isValid) { await apkFile.delete(); throw Exception('SHA-256 verification failed'); } ``` ### 9.3 渠道隔离 - Google Play 版本:不包含任何安装相关权限和代码 - 自建渠道版本:包含完整的下载安装功能 - 使用 Gradle Flavor 在构建层面完全隔离 ### 9.4 应用市场策略 检测应用来源,避免与应用市场升级机制冲突: ```dart final isFromMarket = await AppMarketDetector.isFromAppMarket(); if (isFromMarket) { // 引导用户去应用市场更新 AppMarketDetector.openAppMarketDetail(packageName); } else { // 使用自建更新 selfHostedUpdate(); } ``` --- ## 十、实施步骤 ### Step 1: 添加依赖 ```bash flutter pub add package_info_plus dio in_app_update permission_handler path_provider crypto url_launcher ``` ### Step 2: 创建目录结构 ```bash mkdir -p lib/core/updater/{models,channels} ``` ### Step 3: 创建 Flutter 代码 按照本文档创建以下文件: - models/version_info.dart - version_checker.dart - download_manager.dart - apk_installer.dart - app_market_detector.dart - channels/google_play_updater.dart - channels/self_hosted_updater.dart - update_service.dart ### Step 4: 生成 JSON 序列化代码 ```bash flutter pub run build_runner build --delete-conflicting-outputs ``` ### Step 5: 配置 Android 原生代码 - 修改 MainActivity.kt - 配置 build.gradle - 创建 Flavor 专属 AndroidManifest.xml - 添加 file_paths.xml ### Step 6: 实现后端 API 实现版本检测接口,返回最新版本信息。 ### Step 7: 初始化服务 在 main.dart 中初始化 UpdateService。 ### Step 8: 构建测试 ```bash # Google Play 版本 flutter build apk --flavor googleplay --dart-define=CHANNEL=googleplay --release # 国内版本 flutter build apk --flavor china --dart-define=CHANNEL=china --release ``` ### Step 9: 验证权限 ```bash # 检查 Google Play 版本(不应有安装权限) aapt dump permissions build/app/outputs/flutter-apk/app-googleplay-release.apk | grep INSTALL # 检查国内版本(应有安装权限) aapt dump permissions build/app/outputs/flutter-apk/app-china-release.apk | grep INSTALL ``` ### Step 10: 测试验证 - 测试下载流程 - 测试应用市场检测 - 测试强制更新 - 测试断网场景 --- ## 附录 ### A. 常见问题 | 问题 | 解决方案 | |------|---------| | 下载失败 | 检查网络、确保 HTTPS、添加重试机制 | | 无法安装 | 确认 china flavor 有安装权限 | | SHA-256 校验失败 | 重新下载、检查网络稳定性 | | Google Play 更新不显示 | 确保 versionCode 递增 | ### B. 调试命令 ```bash # 查看 APK 权限 aapt dump permissions app-release.apk # 查看 APK 包名 aapt dump badging app-release.apk | grep package # 生成 SHA-256 sha256sum app-release.apk ``` ### C. 参考资源 - [Flutter 官方文档](https://flutter.dev/docs) - [in_app_update 插件](https://pub.dev/packages/in_app_update) - [Android FileProvider 指南](https://developer.android.com/reference/androidx/core/content/FileProvider) --- 文档版本: 2.0 最后更新: 2024-01-15