Android应用使用内部存储ImageUri显示图片时崩溃问题求助
问题:编辑街头艺术追踪应用条目时未更换图片导致崩溃
我正在开发一款Android应用,用于追踪城市中的街头艺术作品。核心功能如下:
- Main Activity通过RecyclerView展示ArtPiece对象的图片和文字信息,提供添加按钮创建新条目
- 点击添加按钮打开AddActivity,用户可输入文字、通过文件选择器选取图片(Uri以字符串形式存储),保存后条目正常显示在RecyclerView中
- 点击RecyclerView条目进入编辑模式的AddActivity,可修改文字和图片:选择新图片则正常保存,若未更换图片直接保存,应用会崩溃
异常信息
java.lang.SecurityException: Permission Denial: opening provider com.android.providers.media.MediaDocumentsProvider from ProcessRecord{daaa4a9 9467:com.example.graffititracker/u0a186} (pid=9467, uid=10186) requires that you obtain access using ACTION_OPEN_DOCUMENT or related APIs
相关代码
AddActivity代码
class AddActivity : AppCompatActivity() { private lateinit var binding: ActivityAddBinding private var selectedImageUri: Uri? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityAddBinding.inflate(layoutInflater) setContentView(binding.root) val mode = intent.getStringExtra("MODE") val position = intent.getIntExtra("position", -1) var piece = ArtPiece("", Date(1,1,2020), "", "") // 如果是编辑模式,加载已有数据到输入框 if(mode == "EDIT"){ piece = app.myCollection.getPiece(position) binding.txtName.setText(piece.name) binding.btnAddPhoto.setText(R.string.btn_ChangePhoto) } binding.btnSave.setOnClickListener(object: View.OnClickListener{ override fun onClick(v: View?) { if(binding.txtName.text.isNotEmpty()){ val data = Intent() data.putExtra("name", binding.txtName.text.toString()) // 编辑模式下,优先使用新选的图片Uri,否则沿用旧Uri if(mode == "EDIT"){ data.putExtra("position", position) if(selectedImageUri != null) data.putExtra("imageUri", selectedImageUri?.toString()) else data.putExtra("imageUri", piece.image) } else{ if(selectedImageUri != null) data.putExtra("imageUri", selectedImageUri?.toString()) else data.putExtra("imageUri", "") } setResult(RESULT_OK, data) finish() } else{ Toast.makeText(this@AddActivity, "请填写所有字段!", Toast.LENGTH_SHORT).show() } } }) // 选择图片按钮点击事件 binding.btnAddPhoto.setOnClickListener(object: View.OnClickListener{ override fun onClick(v: View?) { // 用ACTION_OPEN_DOCUMENT而非ACTION_PICK避免权限问题(之前用ACTION_PICK编辑时会崩溃) val galleryIntent = Intent(Intent.ACTION_OPEN_DOCUMENT, MediaStore.Images.Media.EXTERNAL_CONTENT_URI) startActivityForResult(galleryIntent, 1) } }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if(requestCode == 1 && resultCode == RESULT_OK){ data?.data?.let{ selectedImageUri = it } } } }
RecyclerView适配器代码
override fun onBindViewHolder(holder: RecyclerViewAdapter.MyViewHolder, position: Int) { val piece = app.myCollection.getPiece(position) if(piece.image != "") holder.imageView.setImageURI(Uri.parse(piece.image)) else holder.imageView.setImageResource(R.drawable.resource_default) holder.tvName.text = piece.name holder.tvDate.text = piece.date.toString() holder.itemView.setOnClickListener{ listener.onItemClick(piece) } holder.itemView.setOnLongClickListener { longClickListener.onItemLongClick(piece) true } }
Manifest权限配置
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
解决方案
问题根源:使用ACTION_OPEN_DOCUMENT获取的Uri仅授予应用临时访问权限,当应用重启或再次复用该Uri时,权限已失效。编辑模式下未更换图片时,直接复用旧Uri会触发权限拒绝异常。
解决步骤:
持久化Uri访问权限
在onActivityResult获取图片Uri后,调用takePersistableUriPermission获取持久访问权限,确保后续可正常访问该Uri:override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { super.onActivityResult(requestCode, resultCode, data) if(requestCode == 1 && resultCode == RESULT_OK){ data?.data?.let{ uri -> selectedImageUri = uri // 获取读写持久权限 val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION contentResolver.takePersistableUriPermission(uri, takeFlags) } } }编辑模式初始化时验证权限
加载已有条目时,检查旧Uri的权限是否有效,若失效则提示用户重新选择图片:if(mode == "EDIT"){ piece = app.myCollection.getPiece(position) binding.txtName.setText(piece.name) binding.btnAddPhoto.setText(R.string.btn_ChangePhoto) // 验证已有Uri的访问权限 val uri = Uri.parse(piece.image) try { contentResolver.openInputStream(uri)?.close() } catch (e: SecurityException) { // 权限失效,清空Uri并提示用户 piece.image = "" Toast.makeText(this, "图片权限已失效,请重新选择", Toast.LENGTH_SHORT).show() } }最优方案:将图片复制到应用私有目录
直接存储文件路径而非系统Uri,彻底绕过权限问题。可添加图片复制方法:private fun copyImageToPrivateDir(uri: Uri): String? { try { val inputStream = contentResolver.openInputStream(uri) val fileName = "art_${System.currentTimeMillis()}.jpg" val outputFile = File(filesDir, fileName) inputStream?.use { input -> outputFile.outputStream().use { output -> input.copyTo(output) } } return outputFile.absolutePath } catch (e: IOException) { e.printStackTrace() return null } }使用时,选择图片后调用此方法存储文件路径,适配器中直接加载文件:
// 选择图片后处理 val filePath = copyImageToPrivateDir(uri) selectedImagePath = filePath // 适配器中展示图片 if(piece.imagePath != ""){ holder.imageView.setImageBitmap(BitmapFactory.decodeFile(piece.imagePath)) } else { holder.imageView.setImageResource(R.drawable.resource_default) }
内容的提问来源于stack exchange,提问作者pralnistroj
相关产品推荐
相关产品推荐

