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

使用CameraX在应用内拍照存图库时遇“操作不被允许”错误

问题描述

相机可正常打开,但拍摄后无法保存图片,报错信息为 java.io.FileNotFoundException: /storage/emulated/0/xxx.jpg: open failed: EPERM (Operation not permitted),已在Manifest中配置相机和存储权限,但问题仍存在。

CameraActivity 代码

public class CameraActivity extends AppCompatActivity {

    private int MY_CAMERA_REQUEST_CODE = 100;
    private TextureView view_finder;
    private Button closeCamBtn;


    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera);

        view_finder = findViewById(R.id.view_finder);

        if (checkSelfPermission(Manifest.permission.CAMERA) != PackageManager.PERMISSION_GRANTED) {
            requestPermissions(new String[]{Manifest.permission.CAMERA}, MY_CAMERA_REQUEST_CODE);
        }
        else{
            startCamera();
        }
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == MY_CAMERA_REQUEST_CODE) {
            if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                Toast.makeText(this, "camera permission granted", Toast.LENGTH_LONG).show();
            } else {
                Toast.makeText(this, "camera permission denied", Toast.LENGTH_LONG).show();
            }
        }
    }


    // always end up in onError()
    private void startCamera() {
        CameraX.unbindAll();

        Rational aspectRatio = new Rational(view_finder.getWidth(), view_finder.getHeight());
        Size screen = new Size(view_finder.getWidth(), view_finder.getHeight());

        PreviewConfig pConfig = new PreviewConfig.Builder().setTargetAspectRatio(aspectRatio).setTargetResolution(screen).build();
        Preview preview = new Preview(pConfig);

        preview.setOnPreviewOutputUpdateListener(
                output -> {
                    ViewGroup parent = (ViewGroup) view_finder.getParent();
                    parent.removeView(view_finder);
                    parent.addView(view_finder);

                    view_finder.setSurfaceTexture(output.getSurfaceTexture());
                }
        );

        ImageCaptureConfig imageCaptureConfig = new ImageCaptureConfig.Builder().setCaptureMode(ImageCapture.CaptureMode.MIN_LATENCY).
                setTargetRotation(getWindowManager().getDefaultDisplay().getRotation()).build();

        final ImageCapture imgCap = new ImageCapture(imageCaptureConfig);

        findViewById(R.id.imgCapture).setOnClickListener(v -> {
            File file = new File(Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".jpg");

            Toast.makeText(getBaseContext(), "File would be: " + Environment.getExternalStorageDirectory() + "/" + System.currentTimeMillis() + ".jpg",Toast.LENGTH_LONG).show();

            imgCap.takePicture(file, new ImageCapture.OnImageSavedListener() {

                @Override
                public void onImageSaved(@NonNull File file) {
                    String msg = "Photo capture succeeded: " + file.getAbsolutePath();
                    Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
                }

                @Override
                public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
                    String msg = "Photo capture failed: " + message;
                    Toast.makeText(getBaseContext(), msg,Toast.LENGTH_LONG).show();
                    if(cause != null){
                        cause.printStackTrace();
                        
                    }
                }
            });
        });
        CameraX.bindToLifecycle(this, preview, imgCap);
    }
}

报错堆栈跟踪

StackTrace:
W/System.err: java.io.FileNotFoundException: /storage/emulated/0/1673950896355.jpg: open failed: EPERM (Operation not permitted)
W/System.err:     at libcore.io.IoBridge.open(IoBridge.java:492)
W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:236)
W/System.err:     at java.io.FileOutputStream.<init>(FileOutputStream.java:186)
W/System.err:     at androidx.camera.core.ImageSaver.run(ImageSaver.java:75)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
W/System.err:     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
W/System.err:     at java.lang.Thread.run(Thread.java:923)
W/System.err: Caused by: android.system.ErrnoException: open failed: EPERM (Operation not permitted)*/
(...)

Manifest 权限配置

<uses-feature android:name="android.hardware.camera.any" />
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

解决方案

1. 补全存储权限申请逻辑

当前代码仅申请了相机权限,未主动申请存储权限,修改权限申请逻辑:

private int PERMISSION_REQUEST_CODE = 100;

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_camera);

    view_finder = findViewById(R.id.view_finder);

    // 同时请求相机和存储权限
    String[] requiredPermissions = {
        Manifest.permission.CAMERA,
        Manifest.permission.WRITE_EXTERNAL_STORAGE,
        Manifest.permission.READ_EXTERNAL_STORAGE
    };

    boolean hasCameraPerm = checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED;
    boolean hasStoragePerm = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED;

    if (!hasCameraPerm || !hasStoragePerm) {
        requestPermissions(requiredPermissions, PERMISSION_REQUEST_CODE);
    } else {
        startCamera();
    }
}

@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
    super.onRequestPermissionsResult(requestCode, permissions, grantResults);
    if (requestCode == PERMISSION_REQUEST_CODE) {
        boolean allGranted = true;
        for (int result : grantResults) {
            if (result != PackageManager.PERMISSION_GRANTED) {
                allGranted = false;
                break;
            }
        }
        if (allGranted) {
            Toast.makeText(this, "权限已获取", Toast.LENGTH_LONG).show();
            startCamera();
        } else {
            Toast.makeText(this, "缺少必要权限,无法使用相机", Toast.LENGTH_LONG).show();
        }
    }
}

2. 适配 Android 10+ 分区存储策略

Android 10(API 29)及以上禁止直接写入外部存储根目录,推荐使用 MediaStore 保存图片到系统图库:

findViewById(R.id.imgCapture).setOnClickListener(v -> {
    ContentValues values = new ContentValues();
    values.put(MediaStore.Images.Media.DISPLAY_NAME, System.currentTimeMillis() + ".jpg");
    values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg");
    // 保存到系统图片目录下的自定义子文件夹
    values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES + "/MyCamera");

    Uri saveUri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values);
    if (saveUri != null) {
        imgCap.takePicture(
            saveUri,
            Executors.newSingleThreadExecutor(),
            new ImageCapture.OnImageSavedListener() {
                @Override
                public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
                    // 回到主线程显示提示
                    runOnUiThread(() -> {
                        String msg = "图片保存成功: " + saveUri.toString();
                        Toast.makeText(getBaseContext(), msg, Toast.LENGTH_LONG).show();
                    });
                    // 通知图库更新,确保图片能立即显示
                    sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, saveUri));
                }

                @Override
                public void onError(@NonNull ImageCapture.UseCaseError useCaseError, @NonNull String message, @Nullable Throwable cause) {
                    runOnUiThread(() -> {
                        String msg = "图片保存失败: " + message;
                        Toast.makeText(getBaseContext(), msg, Toast.LENGTH_LONG).show();
                    });
                    if (cause != null) {
                        cause.printStackTrace();
                    }
                }
            }
        );
    }
});

3. 适配 Android 13+ 权限拆分

若你的应用 targetSdkVersion >=33,需将原存储权限替换为新的媒体权限:

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

同时在权限申请代码中,将 READ_EXTERNAL_STORAGE 替换为 Manifest.permission.READ_MEDIA_IMAGES。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 08:55:20