【重磅发布】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

相关推荐
problc2 小时前
Flutter桌面应用实战:Windows系统代理切换工具开发
windows·flutter
程序员老刘3 小时前
Cursor vs Claude Code vs AS+AI助手:谁才是客户端的编程神器?
flutter·ai编程·客户端
wordbaby10 小时前
Flutter列表渲染的"诡异"问题:为什么我的数据总是第一个?
前端·flutter
恋猫de小郭10 小时前
谷歌开启 Android 开发者身份验证,明年可能开始禁止“未经验证”应用的侧载,要求所有开发者向谷歌表明身份
android·前端·flutter
苏元1 天前
Flutter + GetX:Dio 多接口 401 拦截后跳登录,避免重复跳转和 Controller 找不到问题
flutter
Mhua_Z1 天前
使用 flutter_tts 的配置项
flutter
你听得到111 天前
弹窗库1.1.0版本发布!不止于统一,更是全面的体验升级!
android·前端·flutter
RaidenLiu1 天前
Riverpod 3 :掌握异步任务处理与 AsyncNotifier
前端·flutter
无知的前端1 天前
Flutter 模型转JSON跳过零值/null
flutter·json