RecursiveFileObserver触发删除事件前无法复制文件的技术问题
问题:FileObserver监听删除事件时文件提前被删除,无法完成复制
开发Android应用时,使用RecursiveFileObserver监听文件系统删除事件,需求是触发删除事件时先将目标文件复制到指定路径,再执行删除操作。但实际运行中文件总是在复制完成前就被删除,尝试延迟处理也未解决问题。
核心问题分析
DELETE事件是文件已被删除后才触发,此时文件实体已不存在,无法读取复制。- 原代码中手动调用
deletedFile.delete()属于多余操作,反而会干扰正常逻辑。 - 部分删除操作实际是通过移动文件实现的,单纯监听
DELETE会覆盖不全场景。
解决方案
1. 调整监听事件类型
用MOVED_FROM事件替代/补充DELETE事件:大部分删除操作(如系统删除、APP清理)会先将文件移动到临时目录,MOVED_FROM会在文件被移动前触发,此时文件仍可正常访问。
2. 增加文件存在性重试机制
文件可能处于临时锁定状态,增加短时间重试确保能读取到文件。
3. 替换为更可靠的复制方式
使用Files.copy替代FileChannel.transferFrom,支持原子操作,处理大文件更稳定。
4. 移除手动删除逻辑
文件已被触发删除的进程处理,无需重复执行删除操作。
修改后的关键代码
FileObserverService.java 核心修改
// 调整监听事件,兼容MOVED_FROM和DELETE场景 @Override public void onEvent(int event, File file) { switch (event) { case MOVED_FROM: case DELETE: { Log.d(TAG, "onEvent: File removal triggered " + file.exists()); handleFileDelete(file); } break; default: break; } } // 修改文件处理逻辑 private void handleFileDelete(File targetFile) { // 最多重试3次,每次间隔200ms,避免文件临时不可访问 int retryCount = 0; boolean fileAvailable = false; while (retryCount < 3 && !fileAvailable) { if (targetFile.exists() && targetFile.isFile()) { fileAvailable = true; } else { try { Thread.sleep(200); retryCount++; } catch (InterruptedException e) { e.printStackTrace(); break; } } } if (!fileAvailable) { Log.e(TAG, "File not available after retries: " + targetFile.getAbsolutePath()); return; } try { String fileName = targetFile.getName(); File destDirectory; // 保持原有的文件分类逻辑 if (fileName.contains(".mp4")) { destDirectory = new File(Consts.VID_FILE_DIR); } else if (fileName.contains(".jpg") || fileName.contains(".jpeg") || fileName.contains(".png") || fileName.contains(".webp")) { destDirectory = new File(Consts.IMG_FILE_DIR); } else if (fileName.contains(".aac") || fileName.contains(".mp3")) { destDirectory = new File(Consts.AUD_FILE_DIR); } else if (fileName.contains(".opus") || fileName.contains(".ogg")) { destDirectory = new File(Consts.VOICE_FILE_DIR); } else { destDirectory = new File(Consts.DOC_FILE_DIR); } if (!destDirectory.exists()) { destDirectory.mkdirs(); } File destFile = new File(destDirectory, fileName); // 使用Files.copy实现原子复制,覆盖已存在文件 Files.copy(targetFile.toPath(), destFile.toPath(), StandardCopyOption.REPLACE_EXISTING); // 通知媒体扫描器更新文件索引 Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); mediaScanIntent.setData(Uri.fromFile(destFile)); sendBroadcast(mediaScanIntent); Log.e(TAG, "File copied successfully: " + destFile.getAbsolutePath()); } catch (IOException e) { e.printStackTrace(); Log.e(TAG, "Error copying file: " + e.getMessage()); } // 移除手动删除代码,文件已由原进程处理 }
RecursiveFileObserver.java 修改监听掩码
public RecursiveFileObserver(String path, int mask, EventListener listener) { super(path, mask); mPath = path; // 增加MOVED_FROM事件监听 mMask = mask | FileObserver.CREATE | FileObserver.DELETE_SELF | FileObserver.MOVED_FROM; mListener = listener; }
额外注意事项
- 权限适配:Android 10及以上需适配分区存储,或申请
MANAGE_EXTERNAL_STORAGE权限;Android 9及以下需申请READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限。 - 前台服务:将FileObserverService设置为前台服务,避免被系统后台杀死,确保监听持续有效。
- WhatsApp目录访问:部分版本WhatsApp的状态文件目录受权限限制,可能需要通过
MediaStore或存储访问框架(SAF)获取访问权限。
内容的提问来源于stack exchange,提问作者Hafeez Niazi
相关产品推荐
相关产品推荐

