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

RecursiveFileObserver触发删除事件前无法复制文件的技术问题

问题:FileObserver监听删除事件时文件提前被删除,无法完成复制

开发Android应用时,使用RecursiveFileObserver监听文件系统删除事件,需求是触发删除事件时先将目标文件复制到指定路径,再执行删除操作。但实际运行中文件总是在复制完成前就被删除,尝试延迟处理也未解决问题。

核心问题分析

  1. DELETE事件是文件已被删除后才触发,此时文件实体已不存在,无法读取复制。
  2. 原代码中手动调用deletedFile.delete()属于多余操作,反而会干扰正常逻辑。
  3. 部分删除操作实际是通过移动文件实现的,单纯监听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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 20:48:09