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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:35:25