Android中FileOutputStream访问图片路径遇Permission denied问题如何解决?
解决java.io.FileNotFoundException: Permission denied(外置SD卡写入权限问题)
嘿,我之前也踩过这个外置SD卡写入权限的坑!你的错误很明确——没办法对/storage/6CDB-8541/这个外置SD卡分区里的文件执行写入操作,Android不同版本对外部存储的管控差异很大,我给你整理几个靠谱的解决步骤:
一、先把基础权限配置到位
1. 清单文件加静态权限
打开AndroidManifest.xml,先把必要的权限加上:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <!-- Android 11+ 如需全存储访问,可加这个权限,但需要引导用户手动授权 --> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" android:minSdkVersion="30" />
如果你的APP目标版本是Android 10(API 29),还可以在application标签里加个临时兼容属性,让旧的File API还能凑合用:
android:requestLegacyExternalStorage="true"
不过这个属性在Android 11+会失效,只能算过渡方案。
2. 动态申请权限(Android 6.0+必做)
Android 6.0之后,危险权限需要在运行时申请,不能只靠清单文件。你可以在页面启动时先检查权限:
private static final int REQUEST_STORAGE_PERMISSION = 100; private void checkStoragePermission() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_STORAGE_PERMISSION); } else { // 权限到手,执行你的图片处理逻辑 handleImageProcessing(); } } // 处理权限申请结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_STORAGE_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { handleImageProcessing(); } else { Toast.makeText(this, "得给存储权限才能修改图片哦", Toast.LENGTH_SHORT).show(); } } }
把你原来的图片解码、旋转、写入逻辑都放到handleImageProcessing()里,确保只有权限通过后才执行。
二、外置SD卡的特殊处理(这才是关键!)
你的目标文件在外置SD卡上,Android 10+已经锁死了直接用File API写入外置SD卡私有目录的路径,这时候有两个实用方案:
方案1:用MediaStore API保存图片(推荐给媒体文件)
因为你处理的是相机拍的JPG,用MediaStore是最合规的方式,不用直接操作File:
// 替换你原来的FileOutputStream那段代码 ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, file.getName()); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); // Android 10+ 要设置相对路径 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.put(MediaStore.Images.Media.RELATIVE_PATH, "DCIM/Camera"); values.put(MediaStore.Images.Media.IS_PENDING, 1); } ContentResolver resolver = getContentResolver(); Uri imageUri = resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); try { if (imageUri != null) { OutputStream outputStream = resolver.openOutputStream(imageUri); bmRotated.compress(Bitmap.CompressFormat.JPEG, 100, outputStream); outputStream.flush(); outputStream.close(); // Android 10+ 要标记文件为已完成 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { values.clear(); values.put(MediaStore.Images.Media.IS_PENDING, 0); resolver.update(imageUri, values, null, null); } } } catch (IOException e) { e.printStackTrace(); }
方案2:用Storage Access Framework(SAF)拿目录授权
如果你需要长期对外置SD卡某个目录写文件,可以让用户通过系统文件选择器授权:
private static final int REQUEST_SAF_AUTH = 200; // 启动系统文件选择器,让用户选要操作的SD卡目录 private void requestSdCardDirectoryAccess() { Intent pickDirIntent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); startActivityForResult(pickDirIntent, REQUEST_SAF_AUTH); } // 处理SAF授权结果 @Override protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == REQUEST_SAF_AUTH && resultCode == RESULT_OK && data != null) { Uri treeUri = data.getData(); // 持久化权限,重启APP后依然有效 getContentResolver().takePersistableUriPermission(treeUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); // 找到目标文件 DocumentFile rootDir = DocumentFile.fromTreeUri(this, treeUri); DocumentFile targetImage = rootDir.findFile("IMG_20180523_202102.jpg"); if (targetImage != null) { try { OutputStream os = getContentResolver().openOutputStream(targetImage.getUri()); bmRotated.compress(Bitmap.CompressFormat.JPEG, 100, os); os.flush(); os.close(); } catch (IOException e) { e.printStackTrace(); } } } }
三、给你的代码提两个小优化
- 原来的代码直接从Intent拿Extras,容易空指针,加个判断更稳妥:
Intent receivedIntent = getIntent(); if (receivedIntent != null && receivedIntent.getExtras() != null) { String filePath = receivedIntent.getExtras().getString("file_path"); if (filePath != null) { // 后续逻辑 } }
- 处理完Bitmap记得回收内存,避免内存溢出:
src.recycle(); bmRotated.recycle();
内容的提问来源于stack exchange,提问作者niel
相关产品推荐
相关产品推荐

