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

简述

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


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

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

yaml 复制代码
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 配置

  1. AndroidManifest.xml 完整配置(分场景)
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 直接申请,必须引导用户到系统设置页开启(下面代码会补充)。

  1. Android 14 额外适配(targetSdk 34)

如果你的 build.gradle 中 targetSdkVersion = 34,需添加:

ini 复制代码
<uses-permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"/>

Ios 配置

  1. Info.plist 完整配置(覆盖全场景)
xml 复制代码
<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 审核会拒绝(比如不能只写"访问相册",要说明用途)。

权限管理

dart 复制代码
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;
}

代码关键补充点:

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

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

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

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

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

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

dart 复制代码
// 点击按钮申请权限
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("权限申请失败,请手动开启");
  }
}

避坑指南

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

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

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

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

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

总结

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

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

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

相关推荐
恋猫de小郭3 小时前
Android 官方正式官宣 AI 支持 AppFunctions ,Android 官方 MCP 和系统级 OpenClaw 雏形
android·前端·flutter
MakeZero19 小时前
Flutter那些事-布局篇
flutter
王码码203519 小时前
Flutter for OpenHarmony:socket_io_client 实时通信的事实标准(Node.js 后端的最佳拍档) 深度解析与鸿蒙适配指南
android·flutter·ui·华为·node.js·harmonyos
zhangkai19 小时前
flutter存储知识点总结
flutter·ios
一个假的前端男20 小时前
# 从零开始创建 Flutter Web 项目(附 VS Code 插件推荐)
前端·flutter·react.js
一个假的前端男21 小时前
[特殊字符] Flutter 安装完整指南 Windows—— 2026最新版
windows·flutter
程序员老刘21 小时前
Flutter版本选择指南:3.41 发布,稳定的开年 | 2026年2月
flutter·客户端
恋猫de小郭21 小时前
Flutter 的真正价值是什么?深度解析再结合鸿蒙,告诉你 Flutter 的真正优势
android·前端·flutter
2501_921930831 天前
Flutter for OpenHarmony:三方库实战 animations 页面过渡动画详解
flutter