yx_app_upgrade_flutter/README.md

10 KiB
Raw Blame History

YX App Upgrade Flutter

一款轻量、现代且易用的 Flutter 应用内更新插件。支持 Android 的"下载-安装"全流程iOS 自动跳转 App Store。提供「一键检查更新」与「静默检查 + 用户决定」两种常见用法,并内置完善的权限处理与安装策略。

特性

  • 🎯 智能平台适配Android 直下直装iOS 跳转 App Store
  • 🧠 智能辅助工具:内置 UpgradeAuxiliaryUtils,支持设备类型自动识别与**"稍后更新"状态记忆**(同一版本被跳过不再打扰)。
  • 🔄 灵活的更新策略
    • 一键检查:自动拉取、对比版本、弹窗提示
    • 静默检查:后台获取更新信息,适合冷启动或用户主动点击前的预检查
  • 🎨 现代化 UI
    • Material 风格对话框
    • 📝 富文本支持:更新日志支持粗体、斜体、代码块、高亮等格式
    • 进度与状态可视化
  • 🛡️ 权限适配完善:针对不同 Android 版本的存储、安装、通知权限自动处理
  • 🌐 网络可配置证书校验、超时、默认方法、Headers 等
  • 🔧 安装策略灵活:系统流程/预检查权限/智能策略可选
  • 🏪 应用市场支持:支持多应用市场白名单,智能检测设备已安装的市场
  • 📦 多种更新方式:应用市场、浏览器下载、应用内下载

📦 安装

pubspec.yaml 中添加依赖:

dependencies:
  yx_app_upgrade_flutter: ^1.0.0

🚀 快速开始

方式一:使用辅助工具检查(推荐)

使用 UpgradeAuxiliaryUtils 可以自动处理设备类型判断,并记住用户的"稍后更新"选择(若用户对当前版本点击了稍后更新,下次检查将自动跳过,避免重复打扰)。

import 'package:yx_app_upgrade_flutter/yx_app_upgrade_flutter.dart';

void checkUpdate(BuildContext context) {
  UpgradeAuxiliaryUtils.instance.initiateVersionCheck(
    context,
    // upType: 1 为 Android, 2 为 iOS
    future: (int upType) async {
      // 模拟请求 API
      // final response = await api.checkVersion(type: upType);
      
      return AppUpgradeVersion(
        versionName: "1.2.0",
        versionBuildNumber: 102,
        downloadUrl: "https://example.com/app.apk",
        updateContent: "1. 新增功能\n2. 修复Bug",
        // ... 其他配置
      );
    },
    // 可选配置
    showNoUpdateToast: true, // 无更新时是否提示
    config: UpgradeConfig.production, // 指定环境配置
  );
}

方式二:基础 API (AppUpgradeSimple)

如果您需要更底层的控制,或者不需要"稍后更新"的记忆功能,可以直接使用核心类 AppUpgradeSimple

1. 一键检查更新

import 'package:yx_app_upgrade_flutter/app_upgrade_simple.dart';

// ...
AppUpgradeSimple.instance.checkUpdate(
  context: context,
  future: () async {
    // 调用 API 获取版本信息
    return AppUpgradeVersion(
       // ...
    );
  },
);

2. 静默检查 + 由用户决定

此方式适用于 App 启动时在后台静默检查,仅在有更新时(如显示红点)提示用户。

// 1. 静默检查更新(不显示任何 UI
final upgradeInfo = await AppUpgradeSimple.instance.silentCheckUpdate(
  future: () async {
    return AppUpgradeVersion(...);
  },
);

// 2. 根据结果处理
if (upgradeInfo != null && upgradeInfo.hasUpdate) {
  print('发现新版本: ${upgradeInfo.versionName}');
  // 显示红点,或者在用户点击"检查更新"按钮时调用:
  AppUpgradeSimple.instance.showPreparedUpgrade(
    context: context,
    info: upgradeInfo,
  );
}

常用配置

// 预设配置:
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 能力与富文本

富文本更新日志

更新内容 (updateContent) 支持简单的 Markdown 风格富文本:

  • 粗体**重要内容**
  • 斜体__斜体内容__
  • 代码块`version 2.0`
  • 高亮[特别注意]

支持嵌套组合使用,例如:

  • **[特别注意]**
  • __**重要说明**__
  • 请使用 \version 2.0` 进行灰度验证`
1. 新增 **深色模式** 支持
2. 修复 `Login` 页面崩溃问题
3. [推荐] 性能大幅优化

头部说明块 /{ ... /}

从当前版本开始,updateContent 支持一个可选的头部说明区域,用于展示“本次版本概述 / 重点提示 / 总结说明”等内容。

头部块语法:

  • 起始标记:/{
  • 结束标记:/}
  • 头部块是可选的
  • 头部块可以为空
  • 头部块内继续支持现有富文本标记与嵌套组合

多行写法

/{
本次 **V2.2.0** 版本围绕[使用体验]与[学习管理]进行了多项优化。
__请优先关注__ 工作录入与成长中心改动。
/}
1. 新增草稿箱功能
2. 优化多账号登录机制

单行写法

/{ 本次版本重点优化登录体验与录入效率 /}
1. 新增草稿箱功能
2. 修复已知问题

空头部块写法

/{ /}
1. 第一条更新说明
2. 第二条更新说明

头部块中的嵌套示例

/{
**重要内容**
__[特别注意]__
`version 2.0`
普通说明与 **[高亮重点]** 混合展示
/}

推荐的 updateContent 写法

建议服务端直接返回结构清晰的字符串,头部概述和正文分开:

/{
本次 **V2.2.0** 版本围绕[使用体验]、[账号安全]、[家校联动]、[学习管理]等方面进行多项优化与功能新增,具体更新内容如下:
/}
1. 工作录入新增**草稿箱功能**,录入内容自动暂存;
2. 优化**多账号登录机制**,同一手机号绑定多个账号时可手动选择;
3. 全新上线**家校互动板块**
4. **成长中心**全面调整优化。

解析规则说明

  • 只有当 updateContent 的第一段内容以 /{ 开始时,才会被识别为头部说明块
  • 头部说明块中的内容会单独显示在正文上方
  • 头部中的 ** / __ / \ / []` 标记会继续走现有富文本渲染逻辑
  • 如果正文行本身已经包含 1.一、- 等前缀,插件不会再额外补一个圆点,避免双重列表样式
  • 如果缺少闭合标记 /},插件会回退为普通正文处理,不会抛出异常

调试与预解析

如果你希望在展示前先检查解析结果,可以使用:

final parsed = AppUpgradeSimple.parseUpdateContent(updateContent);

debugPrint('hasExplicitHeaderBlock: ${parsed.hasExplicitHeaderBlock}');
debugPrint('header: ${parsed.header}');
debugPrint('bodyItems: ${parsed.bodyItems.map((e) => e.text).toList()}');

返回结果说明:

  • parsed.header:头部说明内容
  • parsed.hasExplicitHeaderBlock:是否显式使用了 /{ ... /} 头部块
  • parsed.bodyItems:正文条目列表
  • parsed.bodyItems[i].hasLeadingMarker:该条正文是否已经自带列表前缀

对话框特性

  • 发现新版本(强制/非强制)
  • 版本信息卡片
  • 下载进度展示
  • 安装状态检测

⚙️ Android 配置

1) 权限

android/app/src/main/AndroidManifest.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 version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="external_files" path="." />
    <files-path name="files" path="." />
    <cache-path name="cache" path="." />
    <external-cache-path name="external_cache" path="." />
    <external-path name="downloads" path="Download/" />
    <external-files-path name="external_app_files" path="." />
</paths>

📡 服务端返回协议

服务端需要返回包含以下字段的 JSON并在前端转换为 AppUpgradeVersion 对象:

{
  "isForceUpdate": false,
  "versionBuildNumber": 101,
  "versionName": "1.0.1",
  "updateContent": "1. 新增 **深色模式**\n2. 修复 `Bug`",
  "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"]
}

🔧 进阶能力

1) 网络配置

// 自动选择
AppUpgradePlugin().configureHttp(HttpConfig.auto);

// 手动配置
AppUpgradePlugin().configureHttp(const HttpConfig(
  ignoreCertificate: false,
  enableLog: true,
  connectTimeout: 30,
  headers: {'Authorization': 'Bearer xxx'},
));

2) 预下载 APK

final apkPath = await AppUpgradeSimple.instance.preDownloadApk(
  url: 'https://...',
  onProgress: (progress) => print('${progress.percentage}%'),
);

🤝 贡献

欢迎提交 Issue 与 Pull Request

📄 许可证

MIT License