Android 12/13无法选择传递图库视频URI及显示问题求助
问题:Android 12/13从图库选视频后黑屏,触发SecurityException
开发Android应用时,点击图库图标用ACTION_PICK选择视频并传递到编辑界面,在Android 12和13中视频显示黑屏,LogCat抛出如下权限异常:
java.lang.SecurityException: UID 10170 does not have permission to content: //com.google.android.apps.photos.contentprovider/-1/2/content%3A%2F%2Fmedia%2Fexternal%2Fvideo%2Fmedia%2F25/ORIGINAL/NONE/video%2Fmp4/1574077794 [user 0] at android.os.Parcel.createExceptionOrNull
相关代码
VideoFragment.kt
R.id.imageViewVideo -> { if (android.os.Build.VERSION.SDK_INT >= 32) { val intent = Intent(Intent.ACTION_PICK, null) // 仅显示视频 intent.type = "video/*" resultLauncher.launch(intent) } else { requestPermission() } } private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val data: Intent? = result.data val videoUri: Uri = data?.data!! Log.i("videoUri", videoUri.toString()) // content://com.google.android.apps.photos.contentprovider/... val videoPath = parsePath(videoUri) if (videoPath != null) { Log.i("videoPath", videoPath) // /storage/emulated/0/Movies/VID_20231115_163657.mp4 Log.i("videoPath22", videoUri.path.toString()) // /-1/2/content://media/external/video/media/... } val intent = Intent(this@Camera2VideoFragment.requireContext(), SelectVideoActivity::class.java) intent.putExtra("path", videoPath) intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); startActivity(intent) } }
SelectVideoActivity.kt
// onCreate方法中 val source = File(intent!!.getStringExtra("path")!!) copyFile(source, inputFile) Log.i("input file ===", inputFile.toString()) @Throws(IOException::class) fun copyFile(sourceFile: File?, destFile: File) { try { if (!destFile.parentFile!!.exists()) destFile.parentFile!!.mkdirs() if (!destFile.exists()) { destFile.createNewFile() } var source: FileChannel? = null var destination: FileChannel? = null source = FileInputStream(sourceFile).channel destination = FileOutputStream(destFile).channel destination.transferFrom(source, 0, source.size()) } catch (e:Exception){ Log.i("error","selectvideo ${e.message}") } finally { initViews() setListener() } }
低版本Android正常运行情况
文件路径示例:
/storage/emulated/0/DCIM/Camera/VID_20231121_193407.mp4 /storage/emulated/0/Android/data/com.app.packagename/files/app-name/1700718359149.mp4
相关代码:
private lateinit var inputFile: File // onCreate方法中 inputFile = File(getExternalFilesDir("slow-mow")!!.absolutePath.plus("/" + "${System.currentTimeMillis()}.mp4"))
问题原因&修复方案
核心问题
Android 12+(API32+)的分区存储机制限制了直接通过文件路径访问媒体文件的权限,你通过parsePath解析Content URI得到的路径,在高版本系统中并没有有效的访问权限,触发了SecurityException;同时你给启动Intent加了FLAG_GRANT_READ_URI_PERMISSION,但传递的是文件路径而非原始Content URI,导致权限并未生效。
修复步骤
- 直接传递Content URI而非文件路径,确保权限能正确授予给目标Activity
- 使用ContentResolver读取URI内容,替代直接操作FileInputStream的方式,适配高版本权限机制
修改后的代码
VideoFragment.kt的resultLauncher部分
private val resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result -> if (result.resultCode == RESULT_OK) { val data: Intent? = result.data val videoUri: Uri = data?.data ?: return@registerForActivityResult val intent = Intent(requireContext(), SelectVideoActivity::class.java) intent.data = videoUri // 传递原始Content URI intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // 授予读取权限 startActivity(intent) } }
SelectVideoActivity.kt部分
private lateinit var inputFile: File override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ...其他初始化代码 val videoUri: Uri? = intent.data videoUri?.let { inputFile = File(getExternalFilesDir("slow-mow")!!.absolutePath + "/" + "${System.currentTimeMillis()}.mp4") copyFile(it, inputFile) Log.i("input file ===", inputFile.toString()) } } @Throws(IOException::class) fun copyFile(sourceUri: Uri, destFile: File) { try { if (!destFile.parentFile!!.exists()) destFile.parentFile!!.mkdirs() if (!destFile.exists()) { destFile.createNewFile() } // 使用ContentResolver读取URI内容 contentResolver.openInputStream(sourceUri)?.use { inputStream -> FileOutputStream(destFile).use { outputStream -> inputStream.copyTo(outputStream) } } } catch (e: Exception) { Log.i("error", "selectvideo ${e.message}") } finally { initViews() setListener() } }
额外说明
- 不需要再调用
parsePath解析路径,Android 10+推荐始终通过Content URI访问媒体文件 FLAG_GRANT_READ_URI_PERMISSION会让目标Activity获得临时读取该URI的权限,足够完成文件复制操作
内容的提问来源于stack exchange,提问作者sejn
相关产品推荐
相关产品推荐

