切换至getExternalFilesDir()后Android相机功能失效问题
Android 12(API 31)切换存储路径后相机崩溃及图片无法访问问题解决
问题背景
在Android 12.0(API 31)环境下,将拍照存储路径从Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)切换为官方推荐的getExternalFilesDir(Environment.DIRECTORY_PICTURES)后,出现相机应用停止运行、拍摄的图片无法访问的问题,相关核心代码如下:
创建图片文件代码
private File createImageFile() throws IOException { // Create an image file name String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; File storageDir = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); //File storageDir = getExternalFilesDir(null).getAbsoluteFile(); File imageFile = File.createTempFile( imageFileName, /* prefix */ ".jpg", /* suffix */ storageDir /* directory */ ); System.out.print(storageDir); return imageFile; }
相机结果回调代码
public void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode != INPUT_FILE_REQUEST_CODE || mUploadMessage == null) { super.onActivityResult(requestCode, resultCode, data); return; } try { String file_path = mCameraPhotoPath.replace("file:",""); File file = new File(file_path); size = file.length(); Uri x =data.getData(); }catch (Exception e){ Log.e("Error!", "Error while opening image file" + e.getLocalizedMessage()); } if (data != null || mCameraPhotoPath != null) { Integer count = 0; ClipData images = null; try { images = data.getClipData(); }catch (Exception e) { Log.e("Error!", e.getLocalizedMessage()); } if (images == null && data != null && data.getDataString() != null) { count = data.getDataString().length(); } else if (images != null) { count = images.getItemCount(); } Uri[] results = new Uri[count]; // Check that the response is a good one if (resultCode == Activity.RESULT_OK) { if (size != 0) { // If there is not data, then we may have taken a photo if (mCameraPhotoPath != null) { results = new Uri[]{Uri.parse(mCameraPhotoPath)}; } } else if (data.getClipData() == null) { results = new Uri[]{Uri.parse(data.getDataString())}; } else { for (int i = 0; i < images.getItemCount(); i++) { results[i] = images.getItemAt(i).getUri(); } } } mUploadMessage.onReceiveValue(results); mUploadMessage = null; } }
问题原因
- 私有目录权限限制:
getExternalFilesDir()返回的是应用专属的私有存储目录,其他应用(包括系统相机)没有写入权限,直接传递file://Uri会导致相机因权限不足崩溃。 - Uri格式不兼容:Android 7.0(API 24)及以上禁止跨应用传递
file://Uri,必须使用content://格式的Uri,通过FileProvider授权其他应用访问文件。
修复方案
1. 配置FileProvider
在AndroidManifest.xml中注册FileProvider:
<application> ... <provider android:name="androidx.core.content.FileProvider" android:authorities="${applicationId}.fileprovider" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider> ... </application>
在res/xml目录下创建file_paths.xml文件,定义可共享的目录路径:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <external-files-path name="my_pictures" path="Pictures" /> </paths>
这里path="Pictures"对应getExternalFilesDir(Environment.DIRECTORY_PICTURES)的目录。
2. 修改图片文件创建逻辑
更新createImageFile()方法,使用应用私有目录并保存文件路径:
private File createImageFile() throws IOException { String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); String imageFileName = "JPEG_" + timeStamp + "_"; // 使用应用私有Pictures目录 File storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES); File imageFile = File.createTempFile( imageFileName, ".jpg", storageDir ); // 保存图片路径(用于后续访问) mCameraPhotoPath = imageFile.getAbsolutePath(); return imageFile; }
3. 调整相机启动逻辑
启动相机时,通过FileProvider生成content://Uri,并授予相机临时写入权限:
private void dispatchTakePictureIntent() { Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); if (takePictureIntent.resolveActivity(getPackageManager()) != null) { File photoFile = null; try { photoFile = createImageFile(); } catch (IOException ex) { // 处理文件创建异常 } if (photoFile != null) { Uri photoUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", photoFile); takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoUri); // 授予相机临时写入权限 takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION); startActivityForResult(takePictureIntent, INPUT_FILE_REQUEST_CODE); } } }
4. 修改onActivityResult处理逻辑
相机拍照完成后,直接通过保存的文件路径访问图片(使用EXTRA_OUTPUT时data会为空):
public void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode != INPUT_FILE_REQUEST_CODE || mUploadMessage == null) { return; } if (resultCode == Activity.RESULT_OK) { Uri[] results = null; if (mCameraPhotoPath != null) { File photoFile = new File(mCameraPhotoPath); if (photoFile.exists()) { // 生成content://Uri用于后续操作 Uri photoUri = FileProvider.getUriForFile(this, BuildConfig.APPLICATION_ID + ".fileprovider", photoFile); results = new Uri[]{photoUri}; } } mUploadMessage.onReceiveValue(results); mUploadMessage = null; } else { mUploadMessage.onReceiveValue(null); mUploadMessage = null; } }
关键注意事项
- Android 10(API 29)及以上无需申请
WRITE_EXTERNAL_STORAGE权限,应用私有目录访问无需额外权限。 - 确保
file_paths.xml中的路径与getExternalFilesDir()参数对应,避免路径匹配失败。 - 启动相机时必须添加
Intent.FLAG_GRANT_WRITE_URI_PERMISSION,否则相机无法写入文件。
内容的提问来源于stack exchange,提问作者Brian Kunak
相关产品推荐
相关产品推荐

