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

Android API 24及以上调用系统相机存图报错问题咨询

解决Android 7.0+相机拍照文件URI暴露异常问题

兄弟,你遇到的这个问题是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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:29:17