Flutter:无需MANAGE_EXTERNAL_STORAGE权限删除文件并更新MediaStore
解决方案:无需MANAGE_EXTERNAL_STORAGE权限删除音乐文件并更新MediaStore
在Android 10(API 29)及以上的Scoped Storage机制下,直接通过File.deleteSync()操作公共目录(如Download)的文件会受到权限限制,尤其是其他应用创建的文件。要在无MANAGE_EXTERNAL_STORAGE权限的前提下完成删除并同步MediaStore,需通过MediaStore ContentResolver或用户授权的URI来操作,以下是具体实现方案:
方案1:通过MediaStore删除已索引的音乐文件(适用于自身应用创建的文件)
如果目标文件已被MediaStore索引(多数音乐文件会自动被索引),可以通过查询MediaStore获取文件对应的URI,再通过ContentResolver执行删除操作。
Flutter端代码
import 'package:flutter/services.dart'; import 'package:permission_handler/permission_handler.dart'; // 定义MethodChannel与原生通信 const _mediaStoreChannel = MethodChannel('your_app_id/media_store'); Future<void> deleteMusicFile(String filePath, BuildContext context, int index) async { log('Target file path: $filePath'); try { // 适配Android 10以下的存储权限 if (await Permission.storage.request().isGranted) { final bool deleteSuccess = await _mediaStoreChannel.invokeMethod( 'deleteMediaByPath', {'filePath': filePath}, ); if (deleteSuccess) { // 更新本地播放器状态 final playerState = Provider.of<PlayerStateles>(context, listen: false); playerState.updateCurrentIndex(index + 1); snackBarMessage(context); } else { log('Failed to delete file via MediaStore'); } } else { log('Storage permission not granted'); } } catch (e) { log('Delete error: ${e.toString()}'); } }
Android原生代码(Kotlin)
在MainActivity中注册MethodChannel并实现删除逻辑:
import android.content.ContentUris 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_id/media_store" override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { "deleteMediaByPath" -> { val filePath = call.argument<String>("filePath") if (filePath != null) { val success = deleteMediaFile(filePath) result.success(success) } else { result.error("INVALID_ARG", "File path is null", null) } } else -> result.notImplemented() } } } private fun deleteMediaFile(filePath: String): Boolean { // 查询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 mediaUri = ContentUris.withAppendedId(MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, mediaId) // 执行删除操作 return contentResolver.delete(mediaUri, null, null) > 0 } } return false } }
方案2:通过用户授权的URI删除文件(适用于其他应用创建的文件)
如果目标文件是其他应用创建的(如Download目录的第三方下载文件),Android 11+下无法直接通过路径删除,需引导用户通过系统文件选择器选择文件,获取持久URI权限后执行删除。
Flutter端代码
import 'package:file_picker/file_picker.dart'; import 'package:flutter/services.dart'; Future<void> pickAndDeleteMusic(BuildContext context, int index) async { // 启动音频文件选择器 final pickResult = await FilePicker.platform.pickFiles( type: FileType.audio, allowMultiple: false, ); if (pickResult != null && pickResult.files.single.uri != null) { final fileUri = pickResult.files.single.uri; // 请求持久URI权限 await _mediaStoreChannel.invokeMethod( 'requestPersistableUriPerm', {'uri': fileUri.toString()}, ); // 执行删除 final deleteSuccess = await _mediaStoreChannel.invokeMethod( 'deleteByUri', {'uri': fileUri.toString()}, ); if (deleteSuccess) { final playerState = Provider.of<PlayerStateles>(context, listen: false); playerState.updateCurrentIndex(index + 1); snackBarMessage(context); } } }
Android原生代码扩展
在MainActivity的MethodCallHandler中添加以下逻辑:
override fun configureFlutterEngine(flutterEngine: FlutterEngine) { super.configureFlutterEngine(flutterEngine) MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result -> when (call.method) { // ... 保留方案1的deleteMediaByPath逻辑 "requestPersistableUriPerm" -> { val uriStr = call.argument<String>("uri") if (uriStr != null) { val uri = Uri.parse(uriStr) val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(uri, takeFlags) result.success(null) } else { result.error("INVALID_ARG", "URI is null", null) } } "deleteByUri" -> { val uriStr = call.argument<String>("uri") if (uriStr != null) { val uri = Uri.parse(uriStr) val success = contentResolver.delete(uri, null, null) > 0 result.success(success) } else { result.error("INVALID_ARG", "URI is null", null) } } else -> result.notImplemented() } } }
关键说明
- MediaStore自动同步:通过ContentResolver删除文件后,MediaStore会自动更新索引,无需额外调用
scanMedia方法。 - 权限适配:Android 10及以上无需
WRITE_EXTERNAL_STORAGE权限即可操作MediaStore;Android 9及以下仍需申请该权限。 - 第三方文件限制:对于其他应用创建的文件,Android 11+强制要求用户主动选择文件并授权,无法静默删除。
内容的提问来源于stack exchange,提问作者yusuf
相关产品推荐
相关产品推荐

