云计算百科
云计算领域专业知识百科平台

Flutter 存储权限:适配主流系统

文章目录

    • 简述
    • Android 配置
    • Ios 配置
    • 权限管理
    • 避坑指南

在这里插入图片描述

简述

你提供的这套 Flutter 存储权限申请代码和配置,核心逻辑是对的,但缺少关键细节、异常处理、全场景适配(比如 Android 11/12 的特殊逻辑、iOS 细分权限、权限组申请等),我会补充完整的细节,让代码更健壮、适配所有主流系统版本(Android 10-14、iOS 11-17)。


一、补充 pubspec.yaml 依赖(完整)

除了 permission_handler,device_info_plus 是代码中依赖的(判断 Android 版本),需要一起添加,同时指定稳定版本:

dependencies:
flutter:
sdk: flutter
permission_handler: ^11.3.1 # 最新稳定版,修复了Android 14适配问题
device_info_plus: ^10.1.0 # 必须添加,用于判断Android SDK版本
path_provider: ^2.1.2 # 可选,配合权限获取文件路径(常用)

添加后执行:

flutter pub get

Android 配置

  • AndroidManifest.xml 完整配置(分场景)
  • <!– 基础配置:适配所有Android版本 –>
    <manifest >
    <!– 1. 兼容 Android 10 分区存储(可选,关闭Scoped Storage) –>
    <!– 如果需要"访问所有文件",建议保留;仅访问媒体文件可删除 –>
    <application

    android:requestLegacyExternalStorage="true">
    <!– Android 10 适配 –>

    </application>

    <!– 2. 媒体文件权限(基础,所有版本都加) –>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
    android:maxSdkVersion="28"/>
    <!– Android 10+ 自动失效 –>

    <!– 3. Android 13+ 媒体细分权限(必须) –>
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>

    <!– 4. Android 11+ 访问所有文件(仅当需要读写非媒体文件时添加) –>
    <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
    </manifest>

    关键补充说明:

    • android:requestLegacyExternalStorage=“true”:仅对 Android 10(SDK 29)生效,关闭分区存储,兼容旧版权限逻辑;Android 11+ 需用 MANAGE_EXTERNAL_STORAGE。

    • WRITE_EXTERNAL_STORAGE 加 maxSdkVersion=“28”:避免 Android 10+ 重复申请(系统自动忽略)。

    • MANAGE_EXTERNAL_STORAGE 是特殊权限:无法通过 permission_handler 直接申请,必须引导用户到系统设置页开启(下面代码会补充)。

  • Android 14 额外适配(targetSdk 34)
  • 如果你的 build.gradle 中 targetSdkVersion = 34,需添加:

    <uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>

    Ios 配置

  • Info.plist 完整配置(覆盖全场景)
  • <dict>
    <!– 相册读取(基础) –>
    <key>NSPhotoLibraryUsageDescription</key>
    <string>需要访问相册以选择图片/视频</string>

    <!– 相册写入(iOS 11+) –>
    <key>NSPhotoLibraryAddUsageDescription</key>
    <string>需要将图片/视频保存到相册</string>

    <!– iOS 14+ 选择性相册权限(用户可只授权部分照片) –>
    <key>NSPhotoLibraryLimitedUsageDescription</key>
    <string>需要访问您选择的照片/视频</string>

    <!– 本地文件访问(如Documents目录,iOS 11+) –>
    <key>NSDocumentsFolderUsageDescription</key>
    <string>需要访问本地文件以管理您的资料</string>

    <!– 可选:如果需要访问iCloud文件 –>
    <key>NSUbiquitousContainersUsageDescription</key>
    <string>需要访问iCloud文件以同步您的资料</string>
    </dict>

    关键补充:

    • NSPhotoLibraryLimitedUsageDescription:iOS 14+ 新增,用户点击“选择照片”时显示,必须添加,否则权限申请会失败。

    • 描述文案不能为空/太简单:App Store 审核会拒绝(比如不能只写“访问相册”,要说明用途)。

    权限管理

    import 'dart:io';

    import 'package:device_info_plus/device_info_plus.dart';
    import 'package:permission_handler/permission_handler.dart';
    import 'package:flutter/services.dart'; // 用于捕获平台异常

    /// 申请存储/媒体权限(支持全平台、全版本)
    /// [needAllFiles]:是否需要访问"所有文件"(仅Android 11+生效)
    Future<bool> requestStoragePermission({bool needAllFiles = false}) async {
    try {
    // ========== 1. 区分平台和版本 ==========
    if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    final sdkInt = androidInfo.version.sdkInt;

    // Android 13+(SDK 33+):申请媒体细分权限
    if (sdkInt >= 33) {
    // 按需申请:比如只需要图片+视频,就申请这两个
    final photoStatus = await Permission.photos.request();
    final videoStatus = await Permission.videos.request();
    final audioStatus = await Permission.audio.request();
    // 只要需要的权限都授权,就返回true
    return photoStatus.isGranted && videoStatus.isGranted && audioStatus.isGranted;
    }

    // Android 11-12(SDK 30-32):申请存储权限 + 可选"所有文件"
    else if (sdkInt >= 30) {
    // 先申请基础存储权限
    final storageStatus = await Permission.storage.request();
    if (!storageStatus.isGranted) return false;

    // 如果需要访问所有文件,引导到设置页
    if (needAllFiles) {
    final hasManagePerm = await Permission.manageExternalStorage.isGranted;
    if (!hasManagePerm) {
    await openAppSettings(); // 跳转到系统设置页
    // 再次检查权限
    return await Permission.manageExternalStorage.isGranted;
    }
    }
    return true;
    }

    // Android 10-(SDK 29-):传统存储权限
    else {
    final status = await Permission.storage.request();
    return status.isGranted;
    }
    }

    // ========== 2. iOS 适配 ==========
    else if (Platform.isIOS) {
    // iOS 14+ 区分"全部"和"所选",统一申请photos权限即可
    final status = await Permission.photos.request();
    if (status.isGranted || status.isLimited) { // Limited是iOS 14+的"所选照片"权限
    return true;
    } else if (status.isPermanentlyDenied) {
    await openAppSettings(); // 引导到设置页
    return await Permission.photos.isGranted;
    }
    return false;
    }

    // 其他平台(如Linux/Windows)默认返回true
    return true;
    } on PlatformException catch (e) {
    // 捕获平台异常(比如权限插件未初始化)
    print("申请权限失败:${e.message}");
    return false;
    } catch (e) {
    print("申请权限异常:$e");
    return false;
    }
    }

    /// 快速检查存储权限是否已授权
    Future<bool> checkStoragePermission({bool needAllFiles = false}) async {
    if (Platform.isAndroid) {
    final androidInfo = await DeviceInfoPlugin().androidInfo;
    final sdkInt = androidInfo.version.sdkInt;

    if (sdkInt >= 33) {
    return await Permission.photos.isGranted &&
    await Permission.videos.isGranted &&
    await Permission.audio.isGranted;
    } else if (sdkInt >= 30 && needAllFiles) {
    return await Permission.manageExternalStorage.isGranted;
    } else {
    return await Permission.storage.isGranted;
    }
    } else if (Platform.isIOS) {
    final status = await Permission.photos.status;
    return status.isGranted || status.isLimited;
    }
    return true;
    }

    代码关键补充点:

  • 异常捕获:添加 try-catch 捕获 PlatformException(插件调用异常),避免崩溃。

  • Android 11+ 所有文件权限:MANAGE_EXTERNAL_STORAGE 是特殊权限,必须引导用户到设置页开启,无法直接申请。

  • iOS 14+ 有限权限:处理 isLimited 状态(用户只授权部分照片),这是 iOS 14+ 新增的权限类型。

  • 权限组申请:Android 13+ 拆分了媒体权限(照片/视频/音频),按需申请更合规。

  • 工具方法:新增 checkStoragePermission,方便提前检查权限状态。

  • 五、使用示例(完整流程)

    // 点击按钮申请权限
    void onTapRequestPermission() async {
    // 1. 先检查权限
    final hasPerm = await checkStoragePermission(needAllFiles: true);
    if (hasPerm) {
    print("权限已授权,可访问文件");
    return;
    }

    // 2. 申请权限
    final success = await requestStoragePermission(needAllFiles: true);
    if (success) {
    print("权限申请成功");
    // 执行文件操作(如读取相册、读写文件)
    } else {
    print("权限申请失败,请手动开启");
    }
    }

    避坑指南

  • Android 11+ MANAGE_EXTERNAL_STORAGE 审核:
    • Google Play 审核严格,仅允许文件管理器、备份类 App 使用该权限,普通 App 申请会被拒;

    • 优先使用分区存储(Scoped Storage),仅在必要时申请该权限。

  • iOS 权限描述文案:
    • 必须真实描述用途,不能夸大(比如“访问相册用于展示头像”,而不是“访问相册”);

    • 缺少文案会导致 App 崩溃。

  • 权限申请时机:
    • 不要一启动 App 就申请,要在用户触发操作时(比如点击“选择照片”)再申请,提升授权率。
  • targetSdkVersion 适配:
    • 建议设置为 34(Android 14),并测试所有权限逻辑,避免版本兼容问题。

    总结

  • 配置层面:Android 需区分 SDK 版本添加权限,iOS 必须补充完整的权限描述文案;

  • 代码层面:核心是处理 Android 13+ 媒体细分权限、Android 11+ 所有文件权限、iOS 14+ 有限相册权限;

  • 体验层面:权限申请要在用户触发操作时进行,拒绝后引导到设置页,同时做好异常捕获。

  • 这套补充后的代码和配置可以直接用于生产环境,覆盖所有主流系统版本和边界场景。

    赞(0)
    未经允许不得转载:网硕互联帮助中心 » Flutter 存储权限:适配主流系统
    分享到: 更多 (0)

    评论 抢沙发

    评论前必须登录!