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

Android 11+ 环境下Flutter删除媒体文件的权限问题求解

Android 11+ 下Flutter实现媒体文件删除方案

由于Android 11及以上版本的**分区存储(Scoped Storage)**限制,直接通过File API删除公共目录下的媒体文件会失败,必须借助Android系统提供的MediaStore.createDeleteRequest()方法,让用户确认后由系统执行删除操作。以下是具体实现步骤:

一、核心思路

  1. 通过MethodChannel实现Flutter与原生Android的交互
  2. 原生端根据文件路径查询MediaStore对应的Content Uri
  3. 调用createDeleteRequest()生成系统权限请求弹窗
  4. 用户确认后,系统自动完成文件删除,并将结果返回给Flutter端

二、Flutter端代码实现

1. 定义MethodChannel并实现删除调用

import 'package:flutter/services.dart';

class MediaDeleteManager {
  static const MethodChannel _channel = MethodChannel('media_delete_channel');

  // 发起文件删除请求
  static Future<void> requestDeleteFile(String filePath) async {
    try {
      await _channel.invokeMethod('deleteMediaFile', {'filePath': filePath});
    } on PlatformException catch (e) {
      print('删除请求发起失败: ${e.message}');
    }
  }

  // 监听删除结果
  static void setupResultListener(Function(String) onResult) {
    _channel.setMethodCallHandler((call) async {
      if (call.method == 'deleteResult') {
        final String result = call.arguments as String;
        onResult(result);
      }
      return null;
    });
  }
}

2. 使用示例

// 在页面初始化时设置结果监听
@override
void initState() {
  super.initState();
  MediaDeleteManager.setupResultListener((result) {
    print('删除结果: $result');
    // 这里可以更新UI提示用户,比如弹出SnackBar
  });
}

// 触发删除操作
void handleDelete(String filePath) {
  MediaDeleteManager.requestDeleteFile(filePath);
}

三、原生Android端代码实现

1. 在MainActivity中配置MethodChannel

import android.net.Uri
import android.provider.MediaStore
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.IntentSenderRequest
import androidx.activity.result.contract.ActivityResultContracts
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity : FlutterActivity() {
    private val CHANNEL = "media_delete_channel"
    private lateinit var deleteResultLauncher: ActivityResultLauncher<IntentSenderRequest>

    override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        // 注册删除结果回调
        deleteResultLauncher = registerForActivityResult(ActivityResultContracts.StartIntentSenderForResult()) { result ->
            val resultMsg = if (result.resultCode == RESULT_OK) "文件删除成功" else "用户取消删除"
            // 通知Flutter端结果
            MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
                .invokeMethod("deleteResult", resultMsg)
        }

        // 设置MethodChannel处理逻辑
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
            when (call.method) {
                "deleteMediaFile" -> {
                    val filePath = call.argument<String>("filePath")
                    if (filePath.isNullOrEmpty()) {
                        result.error("INVALID_PATH", "文件路径不能为空", null)
                        return@setMethodCallHandler
                    }
                    deleteMediaFile(filePath, result)
                }
                else -> result.notImplemented()
            }
        }
    }

    private fun deleteMediaFile(filePath: String, result: MethodChannel.Result) {
        // 查询MediaStore中对应文件的Uri
        val projection = arrayOf(MediaStore.Audio.Media._ID)
        val selection = "${MediaStore.Audio.Media.DATA} = ?"
        val selectionArgs = arrayOf(filePath)
        contentResolver.query(
            MediaStore.Audio.Media.EXTERNAL_CONTENT_URI,
            projection,
            selection,
            selectionArgs,
            null
        )?.use { cursor ->
            if (cursor.moveToFirst()) {
                val mediaId = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Audio.Media._ID))
                val contentUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mediaId)
                // 创建系统删除请求
                val deleteIntent = MediaStore.createDeleteRequest(contentResolver, listOf(contentUri))
                // 启动权限请求弹窗
                deleteResultLauncher.launch(IntentSenderRequest.Builder(deleteIntent.intentSender).build())
                result.success("已发起删除请求,请等待用户确认")
            } else {
                result.error("FILE_NOT_FOUND", "未在媒体库中找到该文件", null)
            }
        } ?: result.error("QUERY_FAILED", "查询媒体库失败", null)
    }
}

四、权限配置

在AndroidManifest.xml中添加必要权限:

<!-- Android 13及以上版本 -->
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO" />
<!-- Android 11-12版本 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

使用permission_handler请求权限:

import 'package:permission_handler/permission_handler.dart';

Future<bool> requestAudioPermission() async {
  PermissionStatus status;
  if (await Permission.audio.isGranted) {
    return true;
  }
  status = await Permission.audio.request();
  return status.isGranted;
}

注意事项

  1. 仅能删除MediaStore中已收录的媒体文件,若为未被系统扫描到的文件,需先触发媒体库扫描
  2. 无需申请WRITE_EXTERNAL_STORAGE或MANAGE_EXTERNAL_STORAGE权限,符合Google的合规要求
  3. 必须由用户手动确认删除操作,这是系统强制的隐私保护机制

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.04 06:55:18