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

Android 11+(API30+)相机Intent无法正常工作问题排查

Android 11+ 相机拍照后无法加载图片,提示resolveUri failed on bad bitmap uri

问题描述

基于Android官方《相机基础》教程实现的图片选择功能,支持图库与相机选择:

  • 图库选择功能全版本正常运行
  • 相机功能仅在API<30(Android<11)设备上正常
  • API30+设备拍照后无法加载图片,日志输出提示:W/ImageView: resolveUri failed on bad bitmap uri:
  • 已确认无权限缺失,尝试过Picasso库、Bitmap手动处理等方案均无效

复现代码

import android.app.Activity
import android.content.Intent
import android.net.Uri
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.os.Environment
import android.provider.MediaStore
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.content.FileProvider
import simplyquotes.example.myapplication.databinding.ActivityMainBinding
import java.io.File
import java.io.IOException
import java.text.SimpleDateFormat
import java.util.*

class MainActivity : AppCompatActivity() {

    private var _binding: ActivityMainBinding? = null
    private val binding get() = _binding!!

    private var currentPhotoUri: Uri = Uri.EMPTY

    private val intentLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
            if (it.resultCode == Activity.RESULT_OK) {
                val photoResult: Uri? = it.data?.data
                if(photoResult != null) {
                    // 用户选择图库图片
                    this.contentResolver.takePersistableUriPermission(
                        photoResult,
                        Intent.FLAG_GRANT_READ_URI_PERMISSION
                    )
                    currentPhotoUri = photoResult
                    changeProfilePicture(currentPhotoUri)
                } else {
                    // 用户拍照完成
                    changeProfilePicture(currentPhotoUri)
                }
            }
        }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        _binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)
        binding.button.setOnClickListener {
            openIntentChooserForImageSources()
        }
    }

    @Throws(IOException::class)
    private fun createImageFile(): File {
        val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss").format(Date())
        val storageDir = this.getExternalFilesDir(Environment.DIRECTORY_PICTURES)
        val file = File.createTempFile(
            "JPEG_${timeStamp}_", /* 前缀 */
            ".jpg", /* 后缀 */
            storageDir /* 目录 */
        )
        currentPhotoUri = FileProvider.getUriForFile(this.applicationContext ,this.packageName, file)
        return file
    }

    private fun openIntentChooserForImageSources() {
        // 构建图库Intent
        val galleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT, MediaStore.Images.Media.INTERNAL_CONTENT_URI)

        // 构建相机Intent
        val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
        cameraIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
        cameraIntent.also { takePictureIntent ->
            takePictureIntent.resolveActivity(this.packageManager)?.also {
                val photoFile: File? = try {
                    createImageFile()
                } catch (e: IOException){
                    null
                }
                photoFile?.also {
                    val photoFileUri: Uri = FileProvider.getUriForFile(
                        this.applicationContext,
                        this.packageName,
                        it
                    )
                    takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoFileUri)
                }
            }
        }

        val intentChooser = Intent.createChooser(galleryIntent, "Select an app")
        intentChooser.putExtra(Intent.EXTRA_INITIAL_INTENTS, arrayOf(cameraIntent))
        intentLauncher.launch(intentChooser)
    }

    private fun changeProfilePicture(uri: Uri) {
        binding.imageView2.setImageURI(uri)
    }
}

问题原因

  1. 权限传递失效:Android 11+的存储权限机制变更后,使用Intent.createChooser同时启动多个Intent时,相机Intent的FLAG_GRANT_WRITE_URI_PERMISSION无法正确传递给相机应用,导致相机无法写入你通过FileProvider生成的Uri,最终图片文件未被正确创建,currentPhotoUri指向无效路径。
  2. 冗余Uri生成:createImageFile中已经生成了currentPhotoUri,但相机Intent构建时又重复生成了一次Uri,虽然指向同一文件,但增加了权限传递的复杂度。

解决方案

1. 修正权限传递逻辑

修改openIntentChooserForImageSources方法,手动给相机应用授予Uri的读写权限,确保权限正确传递:

private fun openIntentChooserForImageSources() {
    // 图库Intent
    val galleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT, MediaStore.Images.Media.INTERNAL_CONTENT_URI)
    galleryIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)

    // 相机Intent
    val cameraIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)
    var photoFileUri: Uri? = null
    val photoFile: File? = try {
        createImageFile()
    } catch (e: IOException) {
        null
    }
    
    photoFile?.also {
        photoFileUri = FileProvider.getUriForFile(
            this.applicationContext,
            "${this.packageName}.fileprovider", // 需与Manifest中FileProvider的authorities一致
            it
        )
        // 手动给所有能处理相机Intent的应用授予权限
        val resInfoList = packageManager.queryIntentActivities(cameraIntent, android.content.pm.PackageManager.MATCH_DEFAULT_ONLY)
        for (resolveInfo in resInfoList) {
            val packageName = resolveInfo.activityInfo.packageName
            grantUriPermission(packageName, photoFileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION or Intent.FLAG_GRANT_READ_URI_PERMISSION)
        }
        cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoFileUri)
        currentPhotoUri = photoFileUri!! // 统一赋值currentPhotoUri
    }

    // 构建Intent选择器,确保权限传递有效
    val intentList = mutableListOf<Intent>()
    photoFileUri?.let { intentList.add(cameraIntent) }
    intentList.add(galleryIntent)

    val chooserIntent = Intent.createChooser(intentList.removeAt(0), "Select an app")
    chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentList.toTypedArray())
    intentLauncher.launch(chooserIntent)
}

2. 移除冗余Uri生成

修改createImageFile方法,不再内部生成Uri,简化逻辑:

@Throws(IOException::class)
private fun createImageFile(): File {
    val timeStamp: String = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
    val storageDir = getExternalFilesDir(Environment.DIRECTORY_PICTURES)
    return File.createTempFile(
        "JPEG_${timeStamp}_",
        ".jpg",
        storageDir
    )
}

3. 确保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_images" path="Pictures" />
</paths>

验证效果

修改后,API30+设备拍照完成后,相机应用可正确写入图片文件,currentPhotoUri指向有效路径,ImageView能正常加载拍摄的图片。

内容的提问来源于stack exchange,提问作者JustSightseeing

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 00:27:38