Android API 24及以上调用系统相机存图报错问题咨询
兄弟,你遇到的这个问题是Android 7.0(API24)之后的安全机制限制导致的!从7.0开始,系统引入了StrictMode,禁止应用把file://格式的URI暴露给其他第三方应用(比如系统相机),否则就会抛出FileUriExposedException,也就是你日志里看到的file:///sdcard/camera_app/cam_image.jpg exposed beyond app through ClipData.Item.getUri()错误。
下面给你一套完整的解决方案,照着改就能搞定:
步骤1:在AndroidManifest.xml中注册FileProvider
FileProvider是ContentProvider的子类,用来生成content://格式的安全URI,替代原来的file://。在<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>
注意:如果你的项目用的是老的support库,把
androidx.core.content.FileProvider换成android.support.v4.content.FileProvider
步骤2:创建file_paths.xml配置文件
在res/xml目录下新建file_paths.xml(如果没有xml目录就自己创建一个),用来指定允许共享的文件目录:
<?xml version="1.0" encoding="utf-8"?> <paths xmlns:android="http://schemas.android.com/apk/res/android"> <!-- 对应外部存储根目录下的camera_app文件夹 --> <external-path name="camera_app" path="camera_app/" /> </paths>
这里的external-path对应外部存储根目录,path就是你要共享的子文件夹名。
步骤3:修改拍照逻辑代码
原来的代码用Uri.fromFile()生成file://URI,现在换成FileProvider生成content://URI,同时还要处理Android6.0+的动态权限(因为访问外部存储需要WRITE_EXTERNAL_STORAGE权限):
首先添加权限声明(在AndroidManifest.xml中):
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-feature android:name="android.hardware.camera" android:required="true" />
然后修改Activity中的代码:
// 动态权限请求码 private static final int REQUEST_PERMISSION = 100; private static final int CAM_REQUEST = 200; private Button btnCapturePicture; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); btnCapturePicture = findViewById(R.id.btn_capture); final ImageView imgPreview = findViewById(R.id.img_preview); btnCapturePicture.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { // 先检查权限 if (ContextCompat.checkSelfPermission(MainActivity.this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, REQUEST_PERMISSION); return; } try { Intent camera_intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); File file = getFile(); // 用FileProvider生成content:// URI Uri imageUri = FileProvider.getUriForFile(MainActivity.this, getPackageName() + ".fileprovider", file); camera_intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // 给相机app临时授权访问这个URI camera_intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivityForResult(camera_intent, CAM_REQUEST); Log.d("MainActivity", "准备拍照保存"); Toast.makeText(getApplicationContext(), "准备拍照", Toast.LENGTH_SHORT).show(); } catch (Exception e) { Log.d("MainActivity", "error = " + e.getMessage()); Toast.makeText(getApplicationContext(), "error = " + e.getMessage(), Toast.LENGTH_SHORT).show(); } } }); } private File getFile() { // 不要硬编码路径,用系统API获取外部存储目录 File folder = new File(Environment.getExternalStorageDirectory(), "camera_app"); if (!folder.exists()) { // mkdir()只能创建一级目录,mkdirs()可以创建多级目录 folder.mkdirs(); } return new File(folder, "cam_image.jpg"); } @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); if (requestCode == CAM_REQUEST && resultCode == RESULT_OK) { String path = Environment.getExternalStorageDirectory() + "/camera_app/cam_image.jpg"; imgPreview.setImageDrawable(Drawable.createFromPath(path)); } } // 处理权限请求结果 @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == REQUEST_PERMISSION) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 权限同意后,再次触发拍照 btnCapturePicture.performClick(); } else { Toast.makeText(this, "需要存储权限才能保存照片", Toast.LENGTH_SHORT).show(); } } }
关键改动点说明
- 用
FileProvider.getUriForFile()替代Uri.fromFile(),生成安全的content://格式URI - 添加
Intent.FLAG_GRANT_READ_URI_PERMISSION给相机app临时授权访问该URI - 替换硬编码的sdcard路径为
Environment.getExternalStorageDirectory(),适配不同设备 - 增加了动态权限申请逻辑,适配Android6.0+的权限机制
- 把
mkdir()改成mkdirs(),避免目录不存在时创建失败
这样修改后,不管是Android M及以下,还是N及以上版本,都能正常运行了。
内容的提问来源于stack exchange,提问作者Pritesh Vishwakarma

