You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter中如何在Android 13设备的Download文件夹保存文件?

解决Android 13下Flutter保存文件到Download文件夹的权限问题

Android 13(API 33)对存储权限体系做了重大调整:原WRITE_EXTERNAL_STORAGE/READ_EXTERNAL_STORAGE权限被废弃,针对公共Download目录的文件操作逻辑也发生了变化,这是导致你原有代码失效的核心原因。以下是具体解决方案:


核心逻辑调整

1. 权限请求区分Android版本

根据系统版本选择对应的权限检查逻辑,避免在Android 13上请求已失效的Permission.storage:

import 'package:permission_handler/permission_handler.dart';
import 'package:device_info_plus/device_info_plus.dart';

Future<int> _getAndroidSdkVersion() async {
  final androidInfo = await DeviceInfoPlugin().androidInfo;
  return androidInfo.version.sdkInt;
}

Future<void> checkAndRequestStoragePermission() async {
  final sdkVersion = await _getAndroidSdkVersion();

  if (sdkVersion >= 33) {
    // Android 13+:若需直接用File API操作Download目录,需申请MANAGE_EXTERNAL_STORAGE
    final manageStatus = await Permission.manageExternalStorage.status;
    if (manageStatus.isDenied) {
      final result = await Permission.manageExternalStorage.request();
      if (result.isDenied) await openAppSettings();
    } else if (manageStatus.isPermanentlyDenied) {
      await openAppSettings();
    } else {
      // 执行文件操作
    }
  } else {
    // Android 13以下:沿用原有存储权限逻辑
    final storageStatus = await Permission.storage.status;
    if (storageStatus.isDenied) {
      final result = await Permission.storage.request();
      if (result.isDenied) await openAppSettings();
    } else if (storageStatus.isPermanentlyDenied) {
      await openAppSettings();
    } else {
      // 执行文件操作
    }
  }
}

2. 推荐:使用MediaStore保存文件(无需特殊权限)

Android 13允许应用直接向公共Download目录写入文件,无需申请存储权限,推荐通过MediaStore实现(避免依赖特殊权限):

Flutter端代码

import 'dart:typed_data';
import 'package:flutter/services.dart';

Future<bool> saveToDownloads(Uint8List fileBytes, String fileName) async {
  const channel = MethodChannel('your_app/file_save');
  try {
    await channel.invokeMethod('saveFileToDownloads', {
      'bytes': fileBytes,
      'fileName': fileName,
      'mimeType': 'application/octet-stream', // 根据文件类型修改,如text/plain、image/png
    });
    return true;
  } on PlatformException catch (e) {
    print('保存失败:${e.message}');
    return false;
  }
}

Android原生端代码(MainActivity.kt)

import android.content.ContentValues
import android.net.Uri
import android.os.Build
import android.provider.MediaStore
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "your_app/file_save"

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            if (call.method == "saveFileToDownloads") {
                val bytes = call.argument<ByteArray>("bytes")
                val fileName = call.argument<String>("fileName")
                val mimeType = call.argument<String>("mimeType")
                if (bytes != null && fileName != null && mimeType != null) {
                    val success = saveFileToDownloads(bytes, fileName, mimeType)
                    result.success(success)
                } else {
                    result.error("INVALID_ARGS", "参数缺失", null)
                }
            } else {
                result.notImplemented()
            }
        }
    }

    private fun saveFileToDownloads(bytes: ByteArray, fileName: String, mimeType: String): Boolean {
        val contentValues = ContentValues().apply {
            put(MediaStore.Downloads.DISPLAY_NAME, fileName)
            put(MediaStore.Downloads.MIME_TYPE, mimeType)
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                put(MediaStore.Downloads.IS_PENDING, 1)
            }
        }

        contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
            contentResolver.openOutputStream(uri)?.use { it.write(bytes) }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
                contentValues.clear()
                contentValues.put(MediaStore.Downloads.IS_PENDING, 0)
                contentResolver.update(uri, contentValues, null, null)
            }
            return true
        }
        return false
    }
}

权限声明配置

在AndroidManifest.xml中添加对应权限:

<!-- Android 13以下的存储权限 -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

<!-- Android 13+的特殊存储权限(仅当使用File API时需要) -->
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />

注意事项

  1. MANAGE_EXTERNAL_STORAGE是特殊权限,Google Play对其审核严格,仅文件管理器、备份工具等核心场景允许使用,优先用MediaStore方案。
  2. 确保permission_handler和device_info_plus依赖为最新版本,适配Android 13权限体系。

内容的提问来源于stack exchange,提问作者Manish Tiwari

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.20 20:35:02