Android 13无法读取Download目录下手动复制的文本文件问题求助
我刚把平板升级到Android 13,现在应用打不开手动复制到/Download目录下的文本文件。之前应用已经申请了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限,但就算权限已授予,还是会抛出IOException: open failed: EACCES (Permission denied)。
我的应用需求很明确:打开用户复制到/Download目录下的指定文本文件(route.txt),把内容读取到String变量里。谷歌是不是限制了这个已经允许十多年的操作?要通知两万多名用户更改业务逻辑简直是天大的难题。
已尝试的操作
我已经检查并请求了READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE权限(未授权时会发起请求),获取权限后尝试读取文件内容:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
但就算权限已授予,打开文件时还是出现EACCES错误,读取文件的代码如下:
static String readRouteFile(String sFile){ Log.d(TAG, "readRouteFile(" + sFile + ")"); File f = new File(sFile); if(f.exists()){ Log.d(TAG, "readRouteFile.exists"); StringBuilder sb = new StringBuilder(); FileInputStream fis; String sbuf; try { fis = new FileInputStream(f); BufferedReader bReader = new BufferedReader(new InputStreamReader(fis, StandardCharsets.UTF_8)); while ((sbuf = bReader.readLine()) != null ) { sb.append(sbuf).append('\n'); } bReader.close(); fis.close(); Log.d(TAG, "readRouteFile.size:" + sb.length()); return sb.toString(); }catch(IOException e){ Log.e(TAG, "readRouteFile.IOException:" + e.getMessage()); } }else{ Log.e(TAG, "readRouteFile.!exist:" + sFile); } return null; }//readRouteFile
问题原因与解决方案
Android 13对外部存储权限做了细分限制,原有的READ_EXTERNAL_STORAGE不再能访问Download等公共目录下的非媒体文件(比如你的txt文件)。针对你的需求,有两种可行方案:
方案1:用MediaStore API读取文件
Android 13推荐通过MediaStore访问公共目录下的文件,无需额外权限。对于文本文件,可通过MediaStore查询获取文件Uri,再通过ContentResolver读取:
public static String readRouteFileFromMediaStore(Context context) { String[] projection = {MediaStore.Files.FileColumns.DISPLAY_NAME}; String selection = MediaStore.Files.FileColumns.DISPLAY_NAME + " = ?"; String[] selectionArgs = {"route.txt"}; Cursor cursor = context.getContentResolver().query( MediaStore.Downloads.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, null ); if (cursor != null && cursor.moveToFirst()) { int idColumn = cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID); long id = cursor.getLong(idColumn); Uri uri = ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id); cursor.close(); try (InputStream is = context.getContentResolver().openInputStream(uri)) { if (is == null) return null; BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append('\n'); } return sb.toString(); } catch (IOException e) { Log.e(TAG, "readRouteFile.IOException:" + e.getMessage()); } } if (cursor != null) cursor.close(); return null; }
方案2:让用户手动选择文件(ACTION_OPEN_DOCUMENT)
如果用户只需要读取特定的route.txt,可以让用户通过系统文件选择器手动选择,获取Uri后持久化权限,后续无需重复选择:
// 定义请求码 private static final int REQUEST_CODE_OPEN_DOC = 1001; // 启动文件选择器 private void openRouteFilePicker() { Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); intent.addCategory(Intent.CATEGORY_OPENABLE); intent.setType("text/plain"); startActivityForResult(intent, REQUEST_CODE_OPEN_DOC); } // 处理选择结果 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_CODE_OPEN_DOC && resultCode == RESULT_OK && data != null) { Uri uri = data.getData(); // 持久化读取权限 getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION); // 读取文件内容 try (InputStream is = getContentResolver().openInputStream(uri)) { if (is == null) return; BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8)); StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { sb.append(line).append('\n'); } String fileContent = sb.toString(); // 处理读取到的内容 } catch (IOException e) { Log.e(TAG, "readRouteFile.IOException:" + e.getMessage()); } } }
权限失效的核心原因
Android 13开始,READ_EXTERNAL_STORAGE仅能访问媒体文件(图片、视频、音频),无法访问Download目录下的非媒体文件。谷歌这么做是为了强化隐私保护,减少应用对用户文件的无差别访问权限。
内容的提问来源于stack exchange,提问作者KeySmasher

