使用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
相关产品推荐
相关产品推荐

