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

相关推荐
LawrenceLan10 小时前
Flutter 零基础入门(九):构造函数、命名构造函数与 this 关键字
开发语言·flutter·dart
一豆羹11 小时前
macOS 环境下 ADB 无线调试连接失败、Protocol Fault 及端口占用的深度排查
flutter
行者9611 小时前
OpenHarmony上Flutter粒子效果组件的深度适配与实践
flutter·交互·harmonyos·鸿蒙
行者9613 小时前
Flutter与OpenHarmony深度集成:数据导出组件的实战优化与性能提升
flutter·harmonyos·鸿蒙
小雨下雨的雨13 小时前
Flutter 框架跨平台鸿蒙开发 —— Row & Column 布局之轴线控制艺术
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨14 小时前
Flutter 框架跨平台鸿蒙开发 —— Center 控件之完美居中之道
flutter·ui·华为·harmonyos·鸿蒙
小雨下雨的雨15 小时前
Flutter 框架跨平台鸿蒙开发 —— Icon 控件之图标交互美学
flutter·华为·交互·harmonyos·鸿蒙系统
小雨下雨的雨15 小时前
Flutter 框架跨平台鸿蒙开发 —— Placeholder 控件之布局雏形美学
flutter·ui·华为·harmonyos·鸿蒙系统
行者9615 小时前
OpenHarmony Flutter弹出菜单组件深度实践:从基础到高级的完整指南
flutter·harmonyos·鸿蒙
前端不太难16 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios