Android 10下如何将Documents文件夹中的文件复制到应用缓存文件夹?文件存在却返回‘No file found’日志问题求助
问题描述
我正尝试在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




