yx_app_upgrade_flutter/README.md

472 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# App Upgrade Plugin
一款轻量、现代且易用的 Flutter 应用内更新插件。支持 Android 的"下载-安装"全流程iOS 自动跳转 App Store。提供「一键检查更新」与「静默检查 + 用户决定」两种常见用法,并内置完善的权限处理与安装策略。
## ✨ 特性
- **🎯 智能平台适配**Android 直下直装iOS 跳转 App Store
- **🔄 两种更新体验**:非强制(可后台下载)与强制更新(阻塞式对话框)
- **🛡️ 权限适配完善**:针对不同 Android 版本的存储、安装、通知权限自动处理
- **📱 现代化 UI**Material 风格对话框,进度与状态可视化
- **🌐 网络可配置**证书校验、超时、默认方法、Headers 等
- **🔧 安装策略灵活**:系统流程/预检查权限/智能策略可选
- **🏪 应用市场支持**:支持多应用市场白名单,智能检测设备已安装的市场
- **📦 三种更新方式**:应用市场、浏览器下载、应用内下载
## 📦 安装
`pubspec.yaml` 中添加依赖:
```yaml
dependencies:
app_upgrade_plugin: ^1.0.0
```
## 🚀 快速开始
### 方式一:一键检查更新(推荐)
这是最简单的方式,一行代码即可完成检查更新并显示升级对话框:
```dart
import 'package:app_upgrade_plugin/app_upgrade_plugin.dart';
void checkUpdate(BuildContext context) {
// 可选:一次性配置常用选项
AppUpgradeSimple.instance.configure(
const UpgradeConfig(
showNoUpdateToast: true,
autoInstall: false,
),
);
// 一行调用,自动拉取并展示升级对话框
AppUpgradeSimple.instance.checkUpdate(
context: context,
future: () async {
// 调用您的 API 获取版本信息
final response = await http.get('https://your-api.com/check-update');
final data = json.decode(response.body);
// 返回 AppUpgradeVersion 对象
return AppUpgradeVersion(
versionName: data['versionName'],
versionBuildNumber: data['versionBuildNumber'],
isForce: data['isForceUpdate'] ?? false,
updateContent: data['updateContent'],
downloadUrl: data['downloadUrl'],
appStoreUrl: data['appStoreUrl'],
apkSize: data['apkSize'],
apkMd5: data['apkMd5'],
appMarkets: (data['appMarkets'] as List?)
?.map((e) => AppMarket.fromString(e))
.toList(),
supportedMethods: [
AppUpgradeMethod.market,
AppUpgradeMethod.browser,
AppUpgradeMethod.inApp,
],
);
},
);
}
```
### 方式二:静默检查 + 由用户决定
先静默检查是否有更新,然后由用户决定是否显示升级对话框:
```dart
// 注意:当前版本不提供独立的静默检查方法
// 如果需要静默检查,请直接调用您的 API然后手动判断是否需要显示对话框
final response = await http.get('https://your-api.com/check-update');
final data = json.decode(response.body);
// 手动比较版本
final serverVersion = data['versionBuildNumber'];
final currentVersion = await AppUpgradeSimple.instance.getAppInfo();
final currentBuildNumber = int.parse(currentVersion['buildNumber'] ?? '0');
if (serverVersion > currentBuildNumber) {
// 有新版本,显示升级对话框
await AppUpgradeSimple.instance.checkUpdate(
context: context,
future: () async {
return AppUpgradeVersion.fromJson(data);
},
);
}
```
### 常用配置
```dart
// 预设配置:
AppUpgradeSimple.instance.configure(UpgradeConfig.development); // 开发模式(详细日志+提示)
AppUpgradeSimple.instance.configure(UpgradeConfig.production); // 生产模式(静默+性能优化)
// 自定义配置:
AppUpgradeSimple.instance.configure(const UpgradeConfig(
showNoUpdateToast: true,
autoInstall: false,
installTimeout: 60,
enableDebugLog: true,
requireInstallPermission: false, // 默认不需要权限,直接安装
));
```
## 🎨 UI 能力
- **发现新版本对话框**(强制/非强制)
- 强制更新:不可关闭,必须更新
- 非强制更新:可稍后更新,支持后台下载
- **版本信息卡片**显示当前版本、新版本、APK 大小
- **下载进度展示**:实时显示下载进度百分比和状态
- **安装状态检测**:自动检测安装结果,支持重试
- **更新内容展示**:支持 Markdown 格式(粗体、斜体、代码块等)
- **Android 更新方式选择**:应用市场、浏览器下载、应用内下载
## ⚙️ Android 配置
### 1) 权限
`android/app/src/main/AndroidManifest.xml` 中添加:
```xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<!-- Android 13+ 通知权限(可选) -->
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<!-- Android 9 写储存权限SDK 28 及以下需要) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<application>
<!-- FileProviderAndroid 7.0+ APK 安装必需) -->
<provider
android:name="androidx.core.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_paths" />
</provider>
</application>
<!-- Android 11+ 查询声明:允许打开浏览器处理 HTTP/HTTPS URL -->
<queries>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<data android:scheme="http" />
</intent>
</queries>
</manifest>
```
### 2) FileProvider 路径
创建 `android/app/src/main/res/xml/file_paths.xml`
```xml
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<!-- External storage -->
<external-path name="external_files" path="." />
<!-- Internal app storage -->
<files-path name="files" path="." />
<!-- Cache directory -->
<cache-path name="cache" path="." />
<!-- External cache -->
<external-cache-path name="external_cache" path="." />
<!-- Downloads directory -->
<external-path name="downloads" path="Download/" />
<!-- External app-specific files directory (Android/data/包名/files/) -->
<!-- 用于权限被拒绝时,使用应用私有目录存储下载的 APK -->
<external-files-path name="external_app_files" path="." />
</paths>
```
### 3) Gradle可选
```kotlin
android {
compileSdk 34
compileOptions {
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
isCoreLibraryDesugaringEnabled = true
}
}
dependencies {
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.4")
}
```
## 📡 服务端返回协议
服务端需要返回包含以下字段的 JSON关键字段必须
```json
{
"isForceUpdate": false,
"versionBuildNumber": 101,
"versionName": "1.0.1",
"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": ["huawei", "xiaomi", "oppo"],
"supportedMethods": ["market", "browser", "inApp"]
}
```
**字段说明:**
- `versionBuildNumber`(必填):版本号(整数),用于版本比较
- `versionName`(必填):版本名称(字符串),如 "1.0.1"
- `isForceUpdate`(可选):是否强制更新,默认 `false`
- `updateContent`(可选):更新内容说明,支持换行和 Markdown 格式
- `downloadUrl`Android 必填APK 下载地址
- `appStoreUrl`iOS 必填App Store 链接
- `apkSize`可选APK 文件大小(字节)
- `apkMd5`可选APK 文件的 MD5 值,用于校验
- `appMarkets`(可选):应用市场白名单,见下方说明
- `supportedMethods`(可选):支持的更新方式,默认全部支持
**版本比较逻辑:**
插件会优先比较 `versionBuildNumber`,如果相同则比较 `versionName`。如果服务端版本大于当前版本,则判定为有新版本。
### 应用市场白名单 (appMarkets)
`appMarkets` 用于限制支持的应用市场Android作为白名单使用。配置后插件会
1. **检测设备已安装的应用市场**
2. **匹配白名单**:只允许跳转到白名单中且设备已安装的应用市场
3. **智能处理**
- 如果设备上没有白名单中的任何应用市场,提示用户"不支持当前设备的应用市场",引导用户选择其他更新方式(如浏览器下载、应用内下载)
- 如果设备有白名单中的应用市场,直接跳转到设备默认应用市场
**配置示例:**
```dart
// Dart 代码
appMarkets: [AppMarket.huawei, AppMarket.xiaomi, AppMarket.oppo]
// 服务端返回
"appMarkets": ["huawei", "xiaomi", "oppo"]
```
**支持的应用市场类型:**
| 值 | 显示名称 | 对应包名 |
|---|---------|---------|
| `googlePlay` | Google Play | `com.android.vending` |
| `huawei` | 华为应用市场 | `com.huawei.appmarket` |
| `xiaomi` | 小米应用商店 | `com.xiaomi.market` |
| `oppo` | OPPO软件商店 | `com.oppo.market` |
| `vivo` | vivo应用商店 | `com.bbk.appstore` |
| `tencent` | 腾讯应用宝 | `com.tencent.android.qqdownloader` |
| `coolapk` | 酷安 | `com.coolapk.market` |
**不配置 appMarkets 的行为:**
如果不配置 `appMarkets`,插件会使用默认行为:跳转到设备的默认应用市场(使用 `market://` 协议)。
### 支持的更新方式 (supportedMethods)
`supportedMethods` 用于指定支持的更新方式,可选值:
- `market`:应用市场更新
- `browser`:浏览器下载
- `inApp`:应用内下载
如果不配置,默认支持所有方式。如果只配置一种方式,将直接使用该方式,不会显示选择对话框。
## 🔧 进阶能力
### 1) 网络配置
```dart
// 自动选择Debug 绕过证书、Release 严格校验)
AppUpgradePlugin().configureHttp(HttpConfig.auto);
// 开发环境配置(绕过证书验证)
AppUpgradePlugin().configureHttp(HttpConfig.development);
// 生产环境配置(严格证书验证)
AppUpgradePlugin().configureHttp(HttpConfig.production);
// 或手动配置:
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 hasInstall = await PermissionHelper.checkAndRequestInstallPermission(context: context);
// 检查并请求通知权限
final hasNotification = await PermissionHelper.checkAndRequestNotificationPermission(context: context);
// 精确跳转安装权限设置
await AppUpgradePlugin().openInstallPermissionSettings();
```
**权限处理说明:**
- **存储权限**Android 13+ 无需权限使用应用私有目录Android 10-12 会尝试请求但失败时使用私有目录Android 9 及以下需要权限
- **安装权限**:默认情况下(`requireInstallPermission: false`),插件会直接调用系统安装流程,由系统处理权限检查。如果设置为 `true`,会在安装前检查权限
- **通知权限**Android 13+ 需要,用于显示下载进度通知
### 3) 安装策略Android
```dart
// 系统流程(推荐):系统处理权限并弹出安装确认
await AppUpgradePlugin().installApkWithSystemFlow(apkPath);
// 传统安装:需要事先自行处理安装权限
await AppUpgradePlugin().installApk(apkPath);
// 按配置策略安装
await AppUpgradePlugin().installApkWithConfig(
apkPath,
config: InstallConfig.smart,
);
```
**策略对比:**
- `systemFlow`:系统处理权限检查与确认,体验最佳(推荐)
- `preCheckPermission`:先检查权限,无权时返回错误,便于精细控制
- `smart`:有权用预检查,无权走系统流程
### 4) 预下载 APK
```dart
// 后台预下载 APK不显示 UI
final apkPath = await AppUpgradeSimple.instance.preDownloadApk(
url: 'https://your-cdn.com/app-release.apk',
onProgress: (progress) {
print('下载进度: ${progress.percentage}%');
},
);
if (apkPath != null) {
print('下载完成: $apkPath');
}
```
### 5) 查找已下载的 APK
```dart
// 查找指定版本的已下载 APK
final apkPath = await AppUpgradeSimple.instance.findDownloadedApk('1.0.1');
if (apkPath != null) {
// 直接安装
await AppUpgradePlugin().installApkWithSystemFlow(apkPath);
}
```
### 6) 清理下载缓存
```dart
// 清理所有已下载的 APK 文件
await AppUpgradeSimple.instance.clearDownloadCache();
```
## 🐛 常见问题
- **安装失败显示"解析包时出现问题"**:检查 APK 完整性、签名与架构匹配
- **权限申请失败**:确认 Manifest 权限、FileProvider 配置、在 MaterialApp 环境调用
- **下载失败/进度不更新**:检查网络、下载 URL 可用性、服务端是否支持断点续传
- **iOS 不跳转**:确认 `appStoreUrl` 为有效的 App Store 链接
- **"前往浏览器下载"无反应**:确认已在 AndroidManifest.xml 中添加 `<queries>` 声明Android 11+ 必需)
- **FileProvider 配置错误**:确认 `file_paths.xml` 中已添加 `<external-files-path>` 配置,用于权限被拒绝时的备用存储路径
- **Android 9 下载权限错误**:插件会自动检测权限,无权限时使用应用私有目录,无需额外配置
- **安装检测超时**:默认超时时间为 45 秒,可通过 `UpgradeConfig.installTimeout` 调整
## 📚 主要 API 清单
### AppUpgradeSimple推荐使用
- `configure(UpgradeConfig)`:配置升级参数
- `checkUpdate({context, future, showNoUpdateToast, autoInstall, onComplete, config})`:检查更新并显示 UI
- `preDownloadApk({url, onProgress})`:预下载 APKAndroid
- `findDownloadedApk(version)`:查找已下载的 APKAndroid
- `getAppInfo()`:获取当前应用信息
- `clearDownloadCache()`:清理下载缓存
- `checkNetworkStatus()`:检查网络连接状态
### AppUpgradePlugin底层能力
- `configureHttp(HttpConfig)`:网络层配置
- `checkUpdate(url, {params})`:检查更新(返回 UpgradeInfo
- `downloadApk(url, {onProgress, savePath})`:下载 APKAndroid
- `installApk(filePath)`:安装 APK需要先处理权限
- `installApkWithSystemFlow(filePath)`:使用系统流程安装(推荐)
- `installApkWithConfig(filePath, {config})`:按配置策略安装
- `openInstallPermissionSettings()`跳转安装权限设置Android
- `getDeviceInfo()`、`getAndroidSdkVersion()`获取设备信息Android
- `goToAppStore(url, {context})`:跳转到应用商店
- `getInstalledMarkets()`获取设备已安装的应用市场列表Android
- `getDownloadPath()`:获取下载目录路径
- `checkApkExists(version, md5)`:检查指定版本的 APK 是否已下载
### PermissionHelperAndroid
- `checkAndRequestStoragePermission(context)`:检查并请求存储权限
- `checkAndRequestInstallPermission(context)`:检查并请求安装权限
- `checkAndRequestNotificationPermission(context)`:检查并请求通知权限
- `checkInstallPermission()`:检查安装权限状态
### 配置类
- `UpgradeConfig`:升级配置(超时、自动安装、日志等)
- `HttpConfig`HTTP 配置证书、超时、Headers 等)
- `InstallConfig`:安装策略配置
### 模型类
- `AppUpgradeVersion`:服务端返回的版本信息模型
- `UpgradeInfo`:内部使用的升级信息模型
- `AppMarket`:应用市场枚举
- `AppUpgradeMethod`:更新方式枚举
- `DownloadProgress`:下载进度信息
## 🤝 贡献
欢迎提交 Issue 与 Pull Request
## 📄 许可证
MIT License
## 🔗 参考
- [Flutter 官网](https://flutter.dev)
- [Android 安装权限文档](https://developer.android.com/reference/android/Manifest.permission#REQUEST_INSTALL_PACKAGES)
- [FileProvider 使用指南](https://developer.android.com/reference/androidx/core/content/FileProvider)