You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Android 10下如何将Documents文件夹中的文件复制到应用缓存文件夹?文件存在却返回‘No file found’日志问题求助

在Android 10中复制Documents文件夹中非应用创建文件的问题解决方案

问题描述

我正尝试在Android 10中将Documents文件夹中的文件复制到缓存文件夹。由于Android 10必须使用MediaStore,我编写了相关代码,但实际文件存在的情况下,日志却返回‘No file found’。想请教如何复制或读取非本应用创建的文件?

相关代码如下:

File deviceDB = new File(getDatabasePath(Define.DBNAME).toString() + ".db");
Uri contentUri = MediaStore.Files.getContentUri("external");
String selection = MediaStore.MediaColumns.RELATIVE_PATH + "=?";
String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/" + Define.BACKUP_DIR + "/"};
Cursor cursor = getContentResolver().query(contentUri, null, selection, selectionArgs, null);
Uri uri = null;
if (cursor.getCount() == 0) {
    Log.i("debug","No file found");
} else {
    while (cursor.moveToNext()) {
        String fileName = cursor.getString(cursor.getColumnIndex(MediaStore.MediaColumns.DISPLAY_NAME));
        if (fileName.equals(dbName + ".db")) {
            long id = cursor.getLong(cursor.getColumnIndex(MediaStore.MediaColumns._ID));
            uri = ContentUris.withAppendedId(contentUri, id);
            break;
        }
    }
    if (uri == null) {
        Log.i("debug","uri == null");
    } else {
        InputStream inputStream = getContentResolver().openInputStream(uri);
        int size = inputStream.available();
        byte[] bytes = new byte[size];
        inputStream.read(bytes);
        OutputStream output = new FileOutputStream(deviceDB);
        output.write(bytes);
        output.flush();
        inputStream.close();
    }
}

问题分析与解决方案

1. 先搞定权限问题

Android 10里,哪怕用MediaStore读取非本应用创建的文件,也得有READ_EXTERNAL_STORAGE权限。首先在Manifest里声明:

<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

别忘了还要在运行时动态申请这个权限,等用户授权后再去访问文件,不然系统会直接拦截你的请求。

2. 修正RELATIVE_PATH的匹配逻辑

你代码里RELATIVE_PATH的条件带了末尾斜杠/,但MediaStore里存储的路径大概率是不带末尾斜杠的(比如实际存的是Documents/YourBackupDir而非Documents/YourBackupDir/)。你可以试试去掉末尾的斜杠:

String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/" + Define.BACKUP_DIR};

怕有兼容问题的话,也可以用模糊匹配:

String selection = MediaStore.MediaColumns.RELATIVE_PATH + " LIKE ?";
String[] selectionArgs = new String[]{Environment.DIRECTORY_DOCUMENTS + "/" + Define.BACKUP_DIR + "%"};

3. 明确查询的列,避免数据缺失

你现在查询时没指定具体列,可能导致获取的信息不全。建议明确指定需要的列,既高效又能确保拿到关键数据:

String[] projection = {
    MediaStore.MediaColumns._ID,
    MediaStore.MediaColumns.DISPLAY_NAME,
    MediaStore.MediaColumns.RELATIVE_PATH
};
Cursor cursor = getContentResolver().query(contentUri, projection, selection, selectionArgs, null);

4. 优化文件读取的健壮性

原代码里用inputStream.available()获取文件大小很不靠谱,这个方法返回的是当前瞬间能读取的字节数,不是文件总大小。换成循环读取的方式更稳妥,还能避免大文件读取不完整:

if (uri != null) {
    try (InputStream inputStream = getContentResolver().openInputStream(uri);
         OutputStream output = new FileOutputStream(deviceDB)) {
        byte[] buffer = new byte[4096];
        int bytesRead;
        while ((bytesRead = inputStream.read(buffer)) != -1) {
            output.write(buffer, 0, bytesRead);
        }
        output.flush();
    } catch (IOException e) {
        e.printStackTrace();
        Log.e("debug", "文件复制失败: " + e.getMessage());
    }
}

这里用了try-with-resources语法,能自动关闭流,不用手动写close,减少资源泄漏的风险。

5. 试试更合规的SAF方案

Android 10里,对于非媒体文件(比如你的.db文件),除了MediaStore,还可以用**存储访问框架(SAF)**让用户手动选择文件。这种方式不需要申请存储权限,更符合Android的隐私规范。比如通过ACTION_OPEN_DOCUMENT启动文件选择器,拿到用户选中的文件Uri后再复制:

Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT);
intent.addCategory(Intent.CATEGORY_OPENABLE);
intent.setType("*/*"); // 可以指定具体类型,比如application/vnd.sqlite3
startActivityForResult(intent, PICK_FILE_REQUEST_CODE);

然后在onActivityResult里获取Uri,再执行复制操作即可。


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

火山引擎 最新活动