Android 13中DownloadManager返回的Uri无法使用问题求助
问题描述
我尝试将文件下载到外部公共目录,但downloadManager.getUriForDownloadedFile(requestId)返回的Uri无法使用,无法通过它启动ACTION_VIEW Intent,该流程在Android 10中可正常运行。怀疑与Android 13中缺失更新的权限有关,但Logcat无错误日志。目前可通过setDestinationInExternalFilesDir将文件存储到应用私有目录,再用ContentResolver复制到外部媒体存储实现需求,但代码量较大且繁琐。希望找到使用DownloadManager的setDestinationInExternalPublicDir方式下的解决办法。
现有代码
下载请求创建与入队代码:
val request = DownloadManager .Request(Uri.parse(downloadUrl)) .setDestinationInExternalPublicDir( Environment.DIRECTORY_MOVIES, "${UUID.randomUUID()}.mp4" ) .setMimeType("video/mp4") .setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED) .setTitle("Saving...") .setRequiresCharging(false) .setAllowedOverMetered(true) .setAllowedOverRoaming(true) requestId = downloadManager.enqueue(request)
下载完成监听代码:
private val downloadBroadCastReceiver: BroadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { val requestId = intent?.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1) ?: -1 val query = DownloadManager.Query() query.setFilterById(requestId) val cursor = downloadManager.query(query) if (cursor.moveToFirst()) { val columnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS) when (cursor.getInt(columnIndex)) { DownloadManager.STATUS_SUCCESSFUL -> { LOG.info("onReceive: Video download completed!") val uri = downloadManager.getUriForDownloadedFile(requestId) context.startActivity( Intent(Intent.ACTION_VIEW, uri).apply { setDataAndType(uri, "video/mp4") } ) } } } } }
解决办法
1. 适配Android 13媒体权限
Android 13将外部存储权限拆分为媒体类型细分权限,针对视频文件需申请READ_MEDIA_VIDEO权限,在AndroidManifest.xml中添加:
<!-- Android 13+ 视频读取权限 --> <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" android:maxSdkVersion="33" /> <!-- Android 10-12 读取外部存储权限 --> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
同时在运行时动态申请对应权限,危险权限需要用户主动授权。
2. 为Intent添加临时读取权限
DownloadManager返回的Uri默认没有授予其他应用读取权限,需通过Intent.FLAG_GRANT_READ_URI_PERMISSION给视频播放应用临时权限,修改启动Intent的代码:
context?.startActivity( Intent(Intent.ACTION_VIEW).apply { setDataAndType(uri, "video/mp4") // 授予目标应用临时读取权限 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // Android 12+ 建议添加该FLAG避免上下文启动异常 addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } )
3. 从Cursor获取路径转换为MediaStore Uri
如果上述方法无效,可从DownloadManager的查询结果中获取文件绝对路径,再通过MediaStore转换为可共享的Content Uri:
when (cursor.getInt(columnIndex)) { DownloadManager.STATUS_SUCCESSFUL -> { LOG.info("onReceive: Video download completed!") // 从Cursor获取文件本地路径 val pathColumn = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) val filePath = cursor.getString(pathColumn) var fileUri: Uri? = Uri.parse(filePath) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // Android 10+ 通过MediaStore获取合法的Content Uri val projection = arrayOf(MediaStore.Video.Media._ID) val selection = "${MediaStore.Video.Media.DATA} = ?" val selectionArgs = arrayOf(filePath.replace("file://", "")) context?.contentResolver?.query( MediaStore.Video.Media.EXTERNAL_CONTENT_URI, projection, selection, selectionArgs, null )?.use { videoCursor -> if (videoCursor.moveToFirst()) { val id = videoCursor.getLong(videoCursor.getColumnIndexOrThrow(MediaStore.Video.Media._ID)) fileUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id) } } } fileUri?.let { validUri -> context?.startActivity( Intent(Intent.ACTION_VIEW).apply { setDataAndType(validUri, "video/mp4") addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) } ) } } }
4. 验证MimeType与Uri兼容性
确保setMimeType设置的类型与文件实际类型一致,且目标视频应用支持读取content://类型的Uri(Android 10+默认使用该类型Uri)。
内容的提问来源于stack exchange,提问作者Rafsanjani

