From 894e7a44b0ee79840f16d2504e8cc5e420fea4c4 Mon Sep 17 00:00:00 2001 From: "DESKTOP-I3JPKHK\\wy" <1111> Date: Fri, 19 Sep 2025 14:26:48 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E9=83=A8=E5=88=86=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 421 ++++++++++-------------------------- lib/app_upgrade_simple.dart | 2 +- 2 files changed, 117 insertions(+), 306 deletions(-) diff --git a/README.md b/README.md index ced77ea..7295358 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,15 @@ # App Upgrade Plugin -一款功能强大且灵活的 Flutter 应用内更新插件,专为提供符合主流平台用户习惯的无缝升级体验而设计。插件提供了简化版和增强版两套 API,满足不同复杂度的使用需求。 +一款轻量、现代且易用的 Flutter 应用内更新插件。支持 Android 的“下载-安装”全流程,iOS 自动跳转 App Store。提供「一键检查更新」与「静默检查 + 用户决定」两种常见用法,并内置完善的权限处理与安装策略。 -## ✨ 核心特性 +## ✨ 特性 -- **🎯 智能平台适配**:Android 支持完整的下载-安装流程,iOS 自动跳转 App Store -- **🔄 双模式更新**:非强制更新(后台下载)+ 强制更新(阻塞式对话框) -- **🛡️ 全面权限管理**:自动适配不同 Android 版本的存储、安装、通知权限 -- **📱 现代化 UI**:Material Design 风格对话框,支持暗色主题和自定义样式 -- **🌐 健壮网络层**:基于 Dio,支持断点续传、重试机制、证书配置 -- **🔒 安全可靠**:MD5/SHA256 文件校验,防止下载文件损坏 -- **🎨 高度可定制**:丰富的配置选项和回调接口 +- **🎯 智能平台适配**:Android 直下直装,iOS 跳转 App Store +- **🔄 两种更新体验**:非强制(可后台下载)与强制更新(阻塞式对话框) +- **🛡️ 权限适配完善**:针对不同 Android 版本的存储、安装、通知权限 +- **📱 现代化 UI**:Material 风格对话框,进度与状态可视化 +- **🌐 网络可配置**:证书校验、超时、默认方法、Headers 等 +- **🔧 安装策略灵活**:系统流程/预检查权限/智能策略可选 ## 📦 安装 @@ -23,97 +22,88 @@ dependencies: ## 🚀 快速开始 -### 方式一:简化版 API(推荐新手) +### 方式一:一键检查更新(推荐) ```dart import 'package:app_upgrade_plugin/app_upgrade_plugin.dart'; -// 一行代码实现完整升级流程 void checkUpdate(BuildContext context) { + // 可选:一次性配置常用选项 + AppUpgradeSimple.instance.configure( + const UpgradeConfig( + showNoUpdateToast: true, + autoDownload: true, + autoInstall: false, + ), + ); + + // 一行调用,自动拉取并展示升级对话框 AppUpgradeSimple.instance.checkUpdate( context: context, url: 'https://your-api.com/check-update', - params: {'channel': 'release'}, // 可选参数 - showNoUpdateToast: true, - autoDownload: true, - autoInstall: false, + params: {'channel': 'release'}, ); } ``` -### 方式二:分离式 API(更灵活) +### 方式二:静默检查 + 由用户决定 ```dart -// 1. 检查更新(纯逻辑,不涉及UI) -final upgradeInfo = await AppUpgradeSimple.instance.checkUpdateSilent( +final info = await AppUpgradeSimple.instance.checkUpdateSilent( url: 'https://your-api.com/check-update', params: {'platform': 'android'}, ); -// 2. 根据需要显示UI -if (upgradeInfo != null && context.mounted) { - AppUpgradeSimple.instance.showUpgradeDialog( +if (info != null && info.hasUpdate && context.mounted) { + // 再次调用一键方法以展示对话框(内部会重新请求一次最新信息) + await AppUpgradeSimple.instance.checkUpdate( context: context, - info: upgradeInfo, - autoDownload: true, - autoInstall: false, + url: 'https://your-api.com/check-update', ); } ``` -### 方式三:增强版 API(高级用户) +说明:当前公开 API 中未暴露独立的 `showUpgradeDialog()` 方法,静默检查用于“询问后再触发一键流程”的业务场景。 + +### 常用配置 ```dart -final plugin = AppUpgradePluginEnhanced.instance; +// 内置多套配置: +AppUpgradeSimple.instance.configure(UpgradeConfig.auto); // 自动下载+自动安装 +AppUpgradeSimple.instance.configure(UpgradeConfig.silent); // 静默检查 +AppUpgradeSimple.instance.configure(UpgradeConfig.withPermission); // 安装前检查权限(传统) -// 配置插件 -plugin.configure( - debugMode: true, - wifiOnly: false, - autoCheck: true, -); - -// 添加下载监听 -plugin.addDownloadCallback((task) { - print('下载进度: ${task.progress}%'); -}); - -// 智能检查更新 -final info = await plugin.checkUpdateSmart( - 'https://your-api.com/check-update' -); +// 自定义: +AppUpgradeSimple.instance.configure(const UpgradeConfig( + showNoUpdateToast: true, + autoDownload: true, + autoInstall: false, + installTimeout: 60, +)); ``` -## 🎨 UI 展示 +## 🎨 UI 能力 -插件提供多种精美的 UI 组件: - -- **版本更新对话框**:显示版本信息、更新内容、文件大小 -- **下载进度对话框**:实时显示下载进度和状态 -- **应用市场选择**:支持多应用商店选择(华为、小米、OPPO等) -- **权限申请对话框**:友好的权限说明和引导 +- 发现新版本对话框(强制/非强制) +- 版本信息卡片(当前版本、新版本、APK 大小) +- 下载进度展示与可重试安装行为 +- Android 上支持“应用市场选择”或“直接下载”两种路径 ## ⚙️ Android 配置 -### 1. 权限配置 +### 1) 权限 在 `android/app/src/main/AndroidManifest.xml` 中添加: ```xml - - - - - + - + - - - + ``` -### 2. FileProvider 路径配置 +### 2) FileProvider 路径 创建 `android/app/src/main/res/xml/file_paths.xml`: ```xml - - - - - ``` -### 3. Gradle 配置(可选) - -在 `android/app/build.gradle` 中: +### 3) Gradle(可选) ```kotlin android { compileSdk 34 - + compileOptions { sourceCompatibility JavaVersion.VERSION_11 targetCompatibility JavaVersion.VERSION_11 @@ -167,298 +150,126 @@ dependencies { } ``` -## 📡 服务端 API 协议 +## 📡 服务端返回协议 -你的版本检查接口需要返回以下格式的 JSON: +代码模型 `UpgradeInfo` 需要以下字段(关键字段必须): ```json { - "hasUpdate": true, "isForceUpdate": false, - "versionBuildNumber": "101", + "versionBuildNumber": 101, "versionName": "1.0.1", - "updateContent": "1. 修复了登录问题\n2. 优化了界面显示\n3. 提升了性能", + "updateContent": "1. 修复登录\n2. 优化UI\n3. 提升性能", "downloadUrl": "https://your-cdn.com/app-release.apk", "appStoreUrl": "https://apps.apple.com/app/id123456789", "apkSize": 25165824, "apkMd5": "d41d8cd98f00b204e9800998ecf8427e", "appMarkets": [ - { - "name": "华为应用市场", - "packageName": "com.huawei.appmarket", - "url": "appmarket://details?id=com.yourapp.package" - }, - { - "name": "小米应用商店", - "packageName": "com.xiaomi.market", - "url": "mimarket://details?id=com.yourapp.package" - } + {"name":"华为应用市场","packageName":"com.huawei.appmarket","url":"appmarket://details?id=com.yourapp.package"} ] } ``` -### 字段说明 +- `versionBuildNumber` 与 `versionName` 为必填,插件会与当前应用版本进行对比自动计算 `hasUpdate`。 +- `downloadUrl` 仅 Android 需要;`appStoreUrl` 仅 iOS 使用。 -| 字段 | 类型 | 必需 | 说明 | -|------|------|------|------| -| `hasUpdate` | boolean | ✅ | 是否有更新 | -| `isForceUpdate` | boolean | ✅ | 是否强制更新 | -| `versionCode` | string | ✅ | 版本号(数字字符串,用于版本比较) | -| `versionName` | string | ✅ | 版本名称(显示给用户) | -| `updateContent` | string | ✅ | 更新内容描述 | -| `downloadUrl` | string | Android必需 | APK下载地址 | -| `appStoreUrl` | string | iOS必需 | App Store链接 | -| `apkSize` | number | ❌ | APK文件大小(字节) | -| `apkMd5` | string | ❌ | APK文件MD5校验值 | -| `appMarkets` | array | ❌ | 应用市场列表 | +## 🔧 进阶能力 -## 🔧 高级配置 - -### 网络配置 +### 1) 网络配置 ```dart -// 自动模式(推荐) -// Debug: 自动绕过证书验证 -// Release: 严格证书验证 +// 自动选择(Debug 绕过证书、Release 严格校验) +AppUpgradePlugin().configureHttp(HttpConfig.auto); -// 手动配置 -AppUpgradePlugin().configureHttp(HttpConfig( - ignoreCertificate: false, // 是否忽略证书 - enableLog: true, // 启用网络日志 - connectTimeout: 30, // 连接超时(秒) - receiveTimeout: 60, // 接收超时(秒) - defaultMethod: 'GET', // 默认请求方法 - headers: { // 自定义请求头 - 'User-Agent': 'MyApp/1.0', - }, +// 或手动: +AppUpgradePlugin().configureHttp(const HttpConfig( + ignoreCertificate: false, + enableLog: true, + connectTimeout: 30, + receiveTimeout: 60, + defaultMethod: 'GET', + headers: {'User-Agent': 'MyApp/1.0'}, )); ``` -### 权限管理 - -插件提供了独立的权限管理工具: +### 2) 权限助手(Android) ```dart import 'package:app_upgrade_plugin/core/permission_helper.dart'; -// 检查并请求存储权限 -final hasStorage = await PermissionHelper.checkAndRequestStoragePermission( - context: context, -); +final hasStorage = await PermissionHelper.checkAndRequestStoragePermission(context: context); +final hasInstall = await PermissionHelper.checkAndRequestInstallPermission(context: context); +final hasNotification = await PermissionHelper.checkAndRequestNotificationPermission(context: context); -// 检查并请求安装权限 -final hasInstall = await PermissionHelper.checkAndRequestInstallPermission( - context: context, -); - -// 检查并请求通知权限 -final hasNotification = await PermissionHelper.checkAndRequestNotificationPermission( - context: context, -); - -// 直接打开安装权限设置页面(新功能) -final opened = await AppUpgradePlugin().openInstallPermissionSettings(); -if (opened) { - print('成功打开安装权限设置页面'); -} else { - print('无法打开设置页面(非Android设备或系统限制)'); -} +// 精确跳转安装权限设置 +await AppUpgradePlugin().openInstallPermissionSettings(); ``` -#### 精确权限设置跳转 - -相比传统的 `openAppSettings()` 方法会打开应用的全部设置页面,新增的 `openInstallPermissionSettings()` 方法可以更精确地定位到安装权限设置: - -- **Android 8.0+**:直接跳转到当前应用的"安装未知应用"权限页面 -- **Android 8.0-**:跳转到应用详情页面 -- **厂商适配**:针对小米、华为、OPPO、Vivo、三星等定制ROM优化 -- **多重降级**:确保在各种情况下都能成功跳转到相关设置页面 +### 3) 安装策略(Android) ```dart -// 方法1:使用系统流程安装(推荐,类似主流应用) -final systemSuccess = await AppUpgradePlugin().installApkWithSystemFlow(apkPath); -if (systemSuccess) { - print('使用系统流程启动安装'); -} else { - print('系统流程安装失败'); -} +// 系统流程(推荐):系统处理权限并弹出安装确认 +await AppUpgradePlugin().installApkWithSystemFlow(apkPath); -// 方法2:传统安装方式(需要预先检查权限) -final traditionalSuccess = await AppUpgradePlugin().installApk(apkPath); +// 传统安装:需要事先自行处理安装权限 +await AppUpgradePlugin().installApk(apkPath); -// 如果需要手动管理权限,可以使用精确跳转 -final settingsSuccess = await AppUpgradePlugin().openInstallPermissionSettings(); - -// 获取设备信息用于调试 -final deviceInfo = await AppUpgradePlugin().getDeviceInfo(); -print('设备制造商: ${deviceInfo?['manufacturer']}'); -print('Android版本: ${deviceInfo?['release']}'); -``` - -**厂商特殊适配**: - -| 厂商 | 特殊处理 | 预期效果 | -|------|---------|----------| -| 小米/红米 | MIUI权限管理页面 | 直接跳转到应用权限设置 | -| 华为/荣耀 | EMUI/HarmonyOS适配 | 跳转到应用管理页面 | -| OPPO | ColorOS标准方式 | 直接跳转到安装权限 | -| Vivo | FunTouch OS标准方式 | 直接跳转到安装权限 | -| 三星 | One UI标准方式 | 直接跳转到安装权限 | -| 原生Android | 标准Intent | 直接跳转到安装权限 | - -### 两种安装方式对比 - -| 方法 | 权限检查 | 用户体验 | 适用场景 | -|------|---------|----------|----------| -| `installApk()` | 预先检查权限,无权限时返回错误 | 需要开发者处理权限申请 | 需要完全控制安装流程 | -| `installApkWithSystemFlow()` | 让系统处理权限检查 | 类似主流应用,直接弹出安装确认 | 推荐使用,体验更好 | - -**`installApkWithSystemFlow()` 的优势**: -- ✅ 类似盒马、美团等主流应用的安装体验 -- ✅ 系统自动处理权限检查和用户确认 -- ✅ 无需开发者手动管理"安装未知应用"权限 -- ✅ 弹出"XX正尝试安装应用"对话框,用户点击"继续"即可 - -```dart -// 推荐的安装流程 -Future installUpdate(String apkPath) async { - final success = await AppUpgradePlugin().installApkWithSystemFlow(apkPath); - if (success) { - print('安装程序已启动,等待用户确认'); - } else { - print('启动安装程序失败'); - } -} -``` - -### 动态配置安装策略 - -插件提供了灵活的配置系统,可以根据需要选择不同的安装策略: - -```dart -import 'package:app_upgrade_plugin/app_upgrade_plugin.dart'; - -// 方式1:使用预定义配置 -await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.systemFlow); -await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.preCheckPermission); -await AppUpgradePlugin().installApkWithConfig(apkPath, config: InstallConfig.smart); - -// 方式2:自定义配置 -final customConfig = InstallConfig( - strategy: InstallStrategy.smart, - autoOpenSettings: true, - showPermissionRationale: true, - rationaleTitle: '需要安装权限', - rationaleContent: '为了完成应用更新,需要允许安装未知来源的应用。', +// 按配置策略安装 +await AppUpgradePlugin().installApkWithConfig( + apkPath, + config: InstallConfig.smart, ); -await AppUpgradePlugin().installApkWithConfig(apkPath, config: customConfig); - -// 方式3:根据用户设置动态选择 -InstallStrategy getUserPreferredStrategy() { - // 从用户设置或配置文件读取 - final userPreference = getSettingsFromStorage(); - switch (userPreference) { - case 'system': return InstallStrategy.systemFlow; - case 'permission': return InstallStrategy.preCheckPermission; - case 'smart': return InstallStrategy.smart; - default: return InstallStrategy.systemFlow; - } -} - -final strategy = getUserPreferredStrategy(); -final config = InstallConfig(strategy: strategy, autoOpenSettings: true); -await AppUpgradePlugin().installApkWithConfig(apkPath, config: config); ``` -**三种策略对比**: +策略对比: -| 策略 | 行为 | 适用场景 | -|------|------|----------| -| `systemFlow` | 让系统处理所有权限检查 | 希望类似主流应用的体验 | -| `preCheckPermission` | 预先检查权限,无权限时跳转设置 | 需要完全控制权限流程 | -| `smart` | 有权限用预检查,无权限用系统流程 | 平衡控制和体验 | +- `systemFlow`:系统处理权限检查与确认,体验最佳 +- `preCheckPermission`:先检查权限,无权时返回错误,便于精细控制 +- `smart`:有权用预检查,无权走系统流程 -## 🐛 故障排除 +## 🐛 常见问题 -### 常见问题 +- 安装失败显示“解析包时出现问题”:检查 APK 完整性、签名与架构匹配 +- 权限申请失败:确认 Manifest 权限、FileProvider 配置、在 MaterialApp 环境调用 +- 下载失败/进度不更新:检查网络、下载 URL 可用性、服务端是否支持断点 +- iOS 不跳转:确认 `appStoreUrl` 为有效的 App Store 链接 -**Q: 安装失败,提示 "解析包时出现问题"** - -A: 检查以下几点: -- APK 文件是否完整下载(检查文件大小) -- APK 签名是否正确 -- 设备架构是否匹配(armeabi-v7a, arm64-v8a) - -**Q: 权限申请失败** - -A: 确保: -- AndroidManifest.xml 中已声明相应权限 -- FileProvider 配置正确 -- 在 MaterialApp 环境中调用权限申请 - -**Q: 下载失败或进度不更新** - -A: 检查: -- 网络连接是否正常 -- 下载URL是否可访问 -- 服务器是否支持断点续传 - -**Q: iOS 不跳转 App Store** - -A: 确认: -- `appStoreUrl` 字段格式正确 -- URL 为有效的 App Store 链接 - -### 调试技巧 - -开启调试模式以获取详细日志: - -```dart -// 简化版 -AppUpgradeSimple.instance.init(debugMode: true); - -// 增强版 -AppUpgradePluginEnhanced.instance.configure(debugMode: true); -``` - -## 📚 API 文档 +## 📚 主要 API 清单 ### AppUpgradeSimple -| 方法 | 描述 | -|------|------| -| `checkUpdate()` | 检查更新并显示UI | -| `checkUpdateSilent()` | 静默检查更新 | -| `showUpgradeDialog()` | 显示升级对话框 | +- `configure(UpgradeConfig)`:配置升级参数 +- `checkUpdate({context, url, params, ...})`:检查更新并显示 UI +- `checkUpdateSilent({url, params})`:静默检查(不显示 UI) +- `preDownloadApk({url, onProgress})`:预下载 APK(Android) +- `findDownloadedApk(version)`:查找已下载的 APK(Android) +- `getAppInfo()`:获取当前应用信息 -### AppUpgradePluginEnhanced +### AppUpgradePlugin(底层能力) -| 方法 | 描述 | -|------|------| -| `configure()` | 配置插件 | -| `checkUpdateSmart()` | 智能检查更新 | -| `downloadApkSmart()` | 智能下载APK | -| `installApkSmart()` | 智能安装APK | -| `addDownloadCallback()` | 添加下载监听 | +- `configureHttp(HttpConfig)`:网络层配置 +- `downloadApk(url, onProgress)`:下载 APK(Android) +- `installApk(filePath)` / `installApkWithSystemFlow(filePath)` / `installApkWithConfig(filePath, config)`(Android) +- `openInstallPermissionSettings()`:跳转安装权限设置(Android) +- `getDeviceInfo()`、`getAndroidSdkVersion()`(Android) +- `goToAppStore(url)`:跳转到应用商店 -### PermissionHelper +### PermissionHelper(Android) -| 方法 | 描述 | -|------|------| -| `checkAndRequestStoragePermission()` | 存储权限 | -| `checkAndRequestInstallPermission()` | 安装权限 | -| `checkAndRequestNotificationPermission()` | 通知权限 | +- `checkAndRequestStoragePermission(context)` +- `checkAndRequestInstallPermission(context)` +- `checkAndRequestNotificationPermission(context)` ## 🤝 贡献 -欢迎提交 Issue 和 Pull Request! +欢迎提交 Issue 与 Pull Request! ## 📄 许可证 MIT License -## 🔗 相关链接 +## 🔗 参考 - [Flutter 官网](https://flutter.dev) -- [Android 应用安装权限文档](https://developer.android.com/reference/android/Manifest.permission#REQUEST_INSTALL_PACKAGES) +- [Android 安装权限文档](https://developer.android.com/reference/android/Manifest.permission#REQUEST_INSTALL_PACKAGES) - [FileProvider 使用指南](https://developer.android.com/reference/androidx/core/content/FileProvider) \ No newline at end of file diff --git a/lib/app_upgrade_simple.dart b/lib/app_upgrade_simple.dart index db062d6..2988732 100644 --- a/lib/app_upgrade_simple.dart +++ b/lib/app_upgrade_simple.dart @@ -230,7 +230,7 @@ class AppUpgradeSimple { /// 检查网络连接状态 Future checkNetworkStatus() async { try { - final result = await InternetAddress.lookup('google.com'); + final result = await InternetAddress.lookup('baidu.com'); return result.isNotEmpty && result[0].rawAddress.isNotEmpty; } catch (e) { debugPrint('网络检查失败: $e');