React Native Android:RNFS删除文件无报错但未实际删除的问题
问题分析
从Android 10(API 29)开始,系统启用了Scoped Storage机制,限制应用对外部存储公共目录(如Download、Pictures)的直接读写权限。即使你申请了WRITE_EXTERNAL_STORAGE权限,RNFS的unlink方法也无法真正删除公共目录下的文件——它仅能删除应用沙箱内的文件,或仅清除了媒体库的缓存引用,导致文件实际仍保留在设备存储中,文件管理器仍能找到。
解决方案
方案1:临时启用Legacy Storage(仅适配Android 10-12)
在AndroidManifest.xml的application标签中添加配置,强制使用旧版存储模型,让RNFS的unlink直接生效:
<application ... android:requestLegacyExternalStorage="true"> ... </application>
注意:该方案为过渡方案,Android 13及以上系统会忽略此配置,长期来看仍需适配Scoped Storage。
方案2:适配Scoped Storage(推荐,兼容全Android版本)
对于Android 10+,需通过MediaStore API删除公共目录文件,步骤如下:
1. 编写原生Android模块
创建原生模块调用MediaStore的删除逻辑,示例Java代码:
// MediaStoreHelperModule.java package com.yourappname; import android.content.ContentResolver; import android.content.ContentUris; import android.database.Cursor; import android.net.Uri; import android.provider.MediaStore; import androidx.annotation.NonNull; import com.facebook.react.bridge.Promise; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; public class MediaStoreHelperModule extends ReactContextBaseJavaModule { public MediaStoreHelperModule(ReactApplicationContext reactContext) { super(reactContext); } @NonNull @Override public String getName() { return "MediaStoreHelper"; } @ReactMethod public void deleteFile(String filename, String directory, Promise promise) { ContentResolver resolver = getReactApplicationContext().getContentResolver(); Uri collection; String selection; switch (directory) { case "downloads": collection = MediaStore.Downloads.EXTERNAL_CONTENT_URI; selection = MediaStore.Downloads.DISPLAY_NAME + " = ?"; break; case "images": collection = MediaStore.Images.Media.EXTERNAL_CONTENT_URI; selection = MediaStore.Images.Media.DISPLAY_NAME + " = ?"; break; default: promise.reject("INVALID_DIR", "Unsupported directory"); return; } String[] selectionArgs = {filename}; Cursor cursor = resolver.query(collection, new String[]{MediaStore.MediaColumns._ID}, selection, selectionArgs, null); if (cursor != null && cursor.moveToFirst()) { long id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns._ID)); Uri itemUri = ContentUris.withAppendedId(collection, id); int rowsDeleted = resolver.delete(itemUri, null, null); cursor.close(); if (rowsDeleted > 0) { promise.resolve("Successfully deleted " + rowsDeleted + " file(s)"); } else { promise.reject("DELETE_FAILED", "No file found or no permission to delete"); } } else { promise.reject("FILE_NOT_FOUND", "File not found in MediaStore"); } } }
2. 注册原生模块
创建Package类并注册模块:
// MediaStoreHelperPackage.java package com.yourappname; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class MediaStoreHelperPackage implements ReactPackage { @NonNull @Override public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new MediaStoreHelperModule(reactContext)); return modules; } @NonNull @Override public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) { return Collections.emptyList(); } }
在MainApplication.java中添加该Package:
@Override protected List<ReactPackage> getPackages() { List<ReactPackage> packages = new PackageList(this).getPackages(); packages.add(new MediaStoreHelperPackage()); // 添加此行 return packages; }
3. React Native端调用
在TS/JS中调用原生模块完成删除:
import {Platform, NativeModules} from 'react-native'; const {MediaStoreHelper} = NativeModules; const deletePublicFile = async (filename: string, directory: 'downloads' | 'images') => { if (Platform.OS !== 'android') return; try { const result = await MediaStoreHelper.deleteFile(filename, directory); console.log('删除结果:', result); } catch (error) { console.error('删除失败:', error); } }; // 调用示例:删除Download目录下的1.pdf deletePublicFile('1.pdf', 'downloads');
额外注意事项
- Android 13+需申请
READ_MEDIA_IMAGES/READ_MEDIA_VIDEO权限访问媒体文件,删除操作无需额外WRITE权限,但需确保应用已获取对应读取权限。 - 若删除的是应用沙箱内的文件(如
RNFS.DocumentDirectoryPath下的文件),RNFS的unlink可正常工作,无需适配Scoped Storage。
内容的提问来源于stack exchange,提问作者Issam
相关产品推荐
相关产品推荐

