CameraX无法保存图片至相册:出现错误提示‘Stop reading file since a wrong offset...’
CameraX拍照保存失败问题排查与解决
问题描述
已在Manifest配置相机和存储权限,相机页面正常显示,但点击拍照时触发保存失败错误,堆栈跟踪核心报错为java.io.IOException: Could not rename to /storage/emulated/0/Pictures/xxx.jpg.tmp。
原代码
public class CameraActivity extends AppCompatActivity { private final int MY_CAMERA_REQUEST_CODE = 100; private final int MY_EXT_STORAGE_REQUEST_CODE = 101; 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); } if(checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED){ requestPermissions(new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, MY_EXT_STORAGE_REQUEST_CODE); } else{ startCamera(); } // handling cancel-button from camera-activity closeCamBtn = findViewById(R.id.closeCam); closeCamBtn.setOnClickListener(v -> finish()); } @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(); } } if (requestCode == MY_EXT_STORAGE_REQUEST_CODE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "write ext storage permission granted", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "write ext storage permission denied", Toast.LENGTH_LONG).show(); } } } // TODO always yields 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 -> { String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString(); File file = new File(path, "ThisIsATestFile" + System.currentTimeMillis() + ".jpg"); Toast.makeText(getBaseContext(), "File would be: " + file.getAbsolutePath(),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); } }
堆栈跟踪日志
D/Camera: issue capture request for camera 0 D/CaptureSession: Issuing capture request. W/ExifInterface: Stop reading file since a wrong offset may cause an infinite loop: 0 W/ExifInterface: Skip the tag entry since data format (ULONG) is unexpected for tag: GPSAltitudeRef W/ExifInterface: Stop reading file since a wrong offset may cause an infinite loop: 0 W/ExifInterface: Stop reading file since a wrong offset may cause an infinite loop: 0 W/System.err: java.io.IOException: Could not rename to /storage/emulated/0/Pictures/ThisIsATestFile1674077498232.jpg.tmp W/System.err: at androidx.exifinterface.media.ExifInterface.saveAttributes(ExifInterface.java:4307) W/System.err: at androidx.camera.core.Exif.save(Exif.java:132) W/System.err: at androidx.camera.core.ImageSaver.run(ImageSaver.java:91) 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)
解决方法
1. 修复权限申请逻辑漏洞
原代码仅在存储权限已授予时启动相机,若相机和存储权限同时未授予,权限申请后startCamera()不会触发。修改权限处理逻辑:
@Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_camera); view_finder = findViewById(R.id.view_finder); closeCamBtn = findViewById(R.id.closeCam); closeCamBtn.setOnClickListener(v -> finish()); boolean cameraGranted = checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; boolean storageGranted = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; if (!cameraGranted || !storageGranted) { requestPermissions( new String[]{Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 102 // 统一申请请求码 ); } else { startCamera(); } } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); boolean cameraGranted = checkSelfPermission(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED; boolean storageGranted = checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED; if (cameraGranted && storageGranted) { startCamera(); Toast.makeText(this, "权限已全部授予", Toast.LENGTH_LONG).show(); } else { Toast.makeText(this, "缺少必要权限", Toast.LENGTH_LONG).show(); } }
2. 适配Android 10+存储机制
Android 10及以上推荐使用MediaStore保存媒体文件,避免直接操作外部存储的权限限制:
findViewById(R.id.imgCapture).setOnClickListener(v -> { ContentValues values = new ContentValues(); values.put(MediaStore.Images.Media.DISPLAY_NAME, "ThisIsATestFile" + System.currentTimeMillis() + ".jpg"); values.put(MediaStore.Images.Media.MIME_TYPE, "image/jpeg"); values.put(MediaStore.Images.Media.RELATIVE_PATH, Environment.DIRECTORY_PICTURES); Uri uri = getContentResolver().insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, values); if (uri != null) { imgCap.takePicture( new ImageCapture.OutputFileOptions.Builder(getContentResolver(), uri, values).build(), ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() { @Override public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) { String msg = "照片保存成功: " + uri.toString(); Toast.makeText(CameraActivity.this, msg, Toast.LENGTH_LONG).show(); // 通知相册刷新 sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, uri)); } @Override public void onError(@NonNull ImageCaptureException exception) { String msg = "照片保存失败: " + exception.getMessage(); Toast.makeText(CameraActivity.this, msg, Toast.LENGTH_LONG).show(); exception.printStackTrace(); } } ); } });
3. 修复TextureView尺寸初始化问题
startCamera()中直接获取view_finder的宽高可能为0(视图未完成测量),导致预览配置异常,改用post()延迟执行:
private void startCamera() { view_finder.post(() -> { 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()); }); // 其余ImageCapture配置和绑定逻辑保持不变 CameraX.bindToLifecycle(this, preview, imgCap); }); }
4. 确保存储目录存在(可选,针对直接文件操作)
若坚持使用原始文件保存方式,需先确认目录存在:
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString(); File dir = new File(path); if (!dir.exists()) { dir.mkdirs(); // 创建目录及父目录 } File file = new File(dir, "ThisIsATestFile" + System.currentTimeMillis() + ".jpg");
内容的提问来源于stack exchange,提问作者J_R
相关产品推荐
相关产品推荐

