【重磅发布】flutter_chen_updater-版本升级更新

Flutter Chen Updater

一个功能强大的Flutter应用内更新插件,支持Android APK自动下载、安装和iOS跳转App Store。

✨ 特性

  • 跨平台支持: Android APK自动更新,iOS跳转App Store
  • 智能下载: 支持断点续传、文件校验、多重备用方案
  • 权限管理: 自动处理Android安装权限、存储权限
  • 强制更新: 支持可选更新和强制更新模式
  • 进度监控: 实时下载进度回调
  • 文件校验: MD5文件完整性验证
  • 生命周期管理: 智能处理应用前后台切换
  • 自定义UI: 支持自定义更新对话框

效果预览

📦 安装

在你的 pubspec.yaml 文件中添加依赖:

yaml 复制代码
dependencies:
  flutter_chen_updater: ^1.0.0

然后运行:

bash 复制代码
flutter pub get

⚙️ 权限配置

Android

android/app/src/main/AndroidManifest.xml 中添加必要权限:

xml 复制代码
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />

<!-- 存储权限 (Android 9及以下) -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" 
    android:maxSdkVersion="28" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" 
    android:maxSdkVersion="28" />

<!-- 安装权限 -->
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

<!-- 网络状态权限 -->
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

🚀 基础用法

1. 简单更新检查

dart 复制代码
import 'package:flutter_chen_updater/flutter_chen_updater.dart';

void checkForUpdate() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app-v1.1.0.apk',
    iosUrl: 'https://apps.apple.com/app/id123456789',
    description: '1. 修复已知问题\n2. 优化用户体验\n3. 新增功能特性',
    isForceUpdate: false,
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    onAlreadyLatest: () => print('已是最新版本'),
    onConfirm: () => print('用户确认更新'),
    onCancel: () => print('用户取消更新'),
  );
}

2. 带进度监控的更新

dart 复制代码
void checkForUpdateWithProgress() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app-v1.1.0.apk',
    description: '更新说明',
    fileHash: 'abc123def456', // 可选:文件MD5校验
    hashAlgorithm: 'md5',
    fileSize: 15 * 1024 * 1024, // 15MB
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    onProgress: (progress) {
      print('下载进度: ${(progress.progress * 100).toStringAsFixed(1)}%');
      print('已下载: ${progress.downloaded} / ${progress.total}');
    },
    onConfirm: () => print('开始下载更新'),
  );
}

3. 强制更新示例

dart 复制代码
void forceUpdate() {
  final updateInfo = UpdateInfo(
    version: '2.1.0',
    description: '重要安全更新,请立即升级',
    downloadUrl: 'https://example.com/app-v2.1.0.apk',
    isForceUpdate: true, // 强制更新,用户无法取消
  );

  Updater.checkAndUpdate(context, updateInfo);
}

4. 自定义对话框

4.1 使用 dialogBuilder 自定义
dart 复制代码
Future<bool> customDialog(BuildContext context, UpdateInfo updateInfo) {
  return showDialog<bool>(
    context: context,
    barrierDismissible: !updateInfo.isForceUpdate,
    builder: (context) => AlertDialog(
      title: Text('发现新版本 ${updateInfo.version}'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(updateInfo.description),
          if (updateInfo.fileSize != null)
            Padding(
              padding: const EdgeInsets.only(top: 8.0),
              child: Text('大小: ${(updateInfo.fileSize! / 1024 / 1024).toStringAsFixed(1)}MB'),
            ),
        ],
      ),
      actions: [
        if (!updateInfo.isForceUpdate)
          TextButton(
            onPressed: () => Navigator.pop(context, false),
            child: const Text('稍后更新'),
          ),
        ElevatedButton(
          onPressed: () => Navigator.pop(context, true),
          child: const Text('立即更新'),
        ),
      ],
    ),
  ) ?? false;
}

void checkWithCustomDialog() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app.apk',
    description: '重要更新说明',
  );

  Updater.checkAndUpdate(
    context,
    updateInfo,
    dialogBuilder: customDialog,
  );
}
4.2 使用 UpdateDialog 灵活配置
dart 复制代码
void checkWithFlexibleDialog() {
  final updateInfo = UpdateInfo(
    version: '1.2.0',
    downloadUrl: 'https://example.com/app.apk',
    description: '• 修复重要安全漏洞\n• 优化启动速度\n• 新增夜间模式',
    fileSize: 25 * 1024 * 1024, // 25MB
  );

  showDialog<bool>(
    context: context,
    barrierDismissible: !updateInfo.isForceUpdate,
    builder: (context) => UpdateDialog(
      updateInfo: updateInfo,
      // 自定义标题
      title: Container(
        padding: const EdgeInsets.all(16),
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.blue, Colors.purple],
          ),
        ),
        child: Row(
          children: [
            Icon(Icons.system_update, color: Colors.white),
            const SizedBox(width: 8),
            Text(
              '重要更新 V${updateInfo.version}',
              style: const TextStyle(
                color: Colors.white,
                fontWeight: FontWeight.bold,
              ),
            ),
          ],
        ),
      ),
      // 自定义内容
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Container(
            padding: const EdgeInsets.all(16),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Text(
                  '更新内容',
                  style: Theme.of(context).textTheme.titleMedium?.copyWith(
                    fontWeight: FontWeight.bold,
                  ),
                ),
                const SizedBox(height: 8),
                Text(updateInfo.description),
                const SizedBox(height: 12),
                if (updateInfo.fileSize != null)
                  Container(
                    padding: const EdgeInsets.symmetric(
                      horizontal: 12,
                      vertical: 6,
                    ),
                    decoration: BoxDecoration(
                      color: Colors.grey.shade100,
                      borderRadius: BorderRadius.circular(16),
                    ),
                    child: Text(
                      '安装包大小: ${(updateInfo.fileSize! / 1024 / 1024).toStringAsFixed(1)}MB',
                      style: Theme.of(context).textTheme.bodySmall,
                    ),
                  ),
              ],
            ),
          ),
        ],
      ),
      // 自定义底部操作栏
      footer: Container(
        padding: const EdgeInsets.all(16),
        child: Row(
          children: [
            if (!updateInfo.isForceUpdate) ...[
              Expanded(
                child: OutlinedButton(
                  onPressed: () => Navigator.pop(context, false),
                  child: const Text('稍后提醒'),
                ),
              ),
              const SizedBox(width: 12),
            ],
            Expanded(
              flex: updateInfo.isForceUpdate ? 1 : 1,
              child: ElevatedButton.icon(
                onPressed: () => Navigator.pop(context, true),
                icon: const Icon(Icons.download),
                label: const Text('立即更新'),
              ),
            ),
          ],
        ),
      ),
    ),
  );
}

5. 纯下载功能

dart 复制代码
void downloadOnly() {
  final updateInfo = UpdateInfo(
    version: '1.1.0',
    downloadUrl: 'https://example.com/app.apk',
    description: '更新包',
  );

  Updater.download(updateInfo).listen((progress) {
    if (progress.isCompleted && progress.filePath != null) {
      print('下载完成: ${progress.filePath}');
      // 稍后安装
      Updater.installApk(
        progress.filePath!,
        onSuccess: () => print('安装成功'),
        onError: (error) => print('安装失败: $error'),
      );
    } else if (progress.isFailed) {
      print('下载失败: ${progress.error}');
    }
  });
}

6. 版本比较

dart 复制代码
void checkVersionUpdate() {
  final currentVersion = '1.0.0';
  final newVersion = '1.1.0';
  
  final needUpdate = Updater.needUpdate(currentVersion, newVersion);
  print('是否需要更新: $needUpdate');
}

📚 API 文档

UpdateInfo 类

更新信息配置类:

dart 复制代码
class UpdateInfo {
  final String version;           // 新版本号 (必需)
  final String? downloadUrl;      // Android APK下载链接
  final String? iosUrl;          // iOS App Store链接
  final String description;       // 更新说明 (必需)
  final bool isForceUpdate;      // 是否强制更新
  final String? fileHash;        // 文件哈希值 (可选)
  final String? hashAlgorithm;   // 哈希算法 (默认: 'md5')
  final int? fileSize;           // 文件大小 (字节)
  final Map<String, dynamic>? extra; // 额外数据
}

Updater 类

主要更新器类:

静态方法
  • checkAndUpdate() - 检查并更新应用
  • download() - 纯下载方法(不自动安装)
  • installApk() - 安装APK文件
  • needUpdate() - 版本比较
  • cancelDownload() - 取消下载
  • dispose() - 清理资源

DownloadProgress 类

下载进度信息:

dart 复制代码
class DownloadProgress {
  final int downloaded;          // 已下载字节数
  final int total;              // 总字节数
  final double progress;        // 进度 (0.0 - 1.0)
  final DownloadStatus status;  // 下载状态
  final String? error;          // 错误信息
  final String? filePath;       // 文件路径
}

DownloadStatus 枚举

dart 复制代码
enum DownloadStatus {
  idle,         // 空闲
  downloading,  // 下载中
  paused,       // 暂停
  completed,    // 完成
  failed,       // 失败
  cancelled,    // 取消
  installing    // 安装中
}

🔧 高级功能

文件校验

插件支持MD5文件完整性校验:

dart 复制代码
final updateInfo = UpdateInfo(
  version: '1.1.0',
  downloadUrl: 'https://example.com/app.apk',
  description: '安全更新',
  fileHash: 'a1b2c3d4e5f6...',
  hashAlgorithm: 'md5',
);

权限处理

插件自动处理以下权限:

  1. 存储权限 (Android 9及以下)
  2. 安装权限 (Android 8+)
  3. 网络权限

对于缺失权限,插件会:

  • 自动请求权限
  • 引导用户到系统设置页面
  • 监听应用生命周期自动重试

生命周期管理

插件智能处理应用前后台切换:

  • 用户跳转权限设置后返回应用时自动重试安装
  • 后台下载任务保持活跃
  • 应用退出时自动清理资源

💡 错误处理

插件提供完善的错误处理机制:

dart 复制代码
Updater.checkAndUpdate(
  context,
  updateInfo,
  onProgress: (progress) {
    if (progress.isFailed) {
      // 处理下载失败
      showDialog(
        context: context,
        builder: (_) => AlertDialog(
          title: const Text('下载失败'),
          content: Text(progress.error ?? '未知错误'),
          actions: [
            TextButton(
              onPressed: () => Navigator.pop(context),
              child: const Text('确定'),
            ),
          ],
        ),
      );
    }
  },
);

🚦 最佳实践

  1. 版本检查: 建议在应用启动时检查更新
  2. 网络判断: 在检查更新前判断网络状态
  3. 用户体验: 避免在关键操作时弹出更新提示
  4. 资源清理 : 应用退出时调用 Updater.dispose()
  5. 错误日志: 记录更新过程中的错误信息用于调试

❓ 常见问题

Q: Android安装失败怎么办?

A: 检查是否授予了"安装未知应用"权限,插件会自动引导用户设置。

Q: 下载速度慢怎么办?

A: 插件使用Android系统下载管理器和HTTP备用方案,确保最佳下载体验。

Q: 如何支持增量更新?

A: 当前版本不支持增量更新,建议使用文件校验确保完整性。

Q: iOS如何实现自动更新?

A: iOS由于系统限制只能跳转App Store,无法实现APK式的自动更新。

⚠️ 注意事项

  1. Android权限

    • Android 6.0+ 需要运行时申请存储权限
    • Android 8.0+ 需要安装未知来源应用权限
    • Android 10+ 使用作用域存储,无需存储权限
  2. 文件安全

    • 建议使用HTTPS下载链接
    • 强烈推荐设置fileHash进行文件完整性校验
    • 下载的APK文件会自动进行基础验证
  3. 用户体验

    • 避免在用户进行重要操作时弹出更新提示
    • 强制更新应谨慎使用,仅在安全更新时使用
    • 提供清晰的更新说明和文件大小信息

📄 许可证

MIT License

相关推荐
孤鸿玉11 小时前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥17 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng2 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭2 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯2 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan2 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓2 天前
Flutter Getx 的页面传参
flutter
火柴就是我3 天前
flutter 之真手势冲突处理
android·flutter
Speed1233 天前
`mockito` 的核心“打桩”规则
flutter·dart