Android API21+获取Documents目录下PDF文件Uri的兼容方案
兼容API21+获取公共Documents目录PDF文件的Uri用于通知跳转
核心思路
Android 10(API29)及以上启用分区存储后,file:// Uri会触发FileUriExposedException,公共Documents目录的文件需通过MediaStore查询或DownloadManager直接获取content://类型的Uri,才能安全地在跨应用场景(如通知跳转打开PDF)中使用。
方案1:通过DownloadManager下载ID获取Uri(最可靠)
如果保存了DownloadManager返回的下载ID,直接查询该ID对应的文件Uri,避免文件名匹配错误:
fun getDownloadedPdfUri(context: Context, downloadId: Long): Uri? { val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager val query = DownloadManager.Query().setFilterById(downloadId) downloadManager.query(query)?.use { cursor -> if (cursor.moveToFirst()) { val uriColumnIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI) return Uri.parse(cursor.getString(uriColumnIndex)) } } return null }
方案2:通过MediaStore查询文件名获取Uri
如果没有保存下载ID,可通过MediaStore索引公共Documents目录的文件,匹配文件名和MIME类型获取Uri:
fun getPdfUriByFileName(context: Context, fileName: String): Uri? { val projection = arrayOf( MediaStore.Files.FileColumns._ID, MediaStore.Files.FileColumns.DISPLAY_NAME ) // 匹配文件名和PDF类型 val selection = """ ${MediaStore.Files.FileColumns.DISPLAY_NAME} = ? AND ${MediaStore.Files.FileColumns.MIME_TYPE} = ? """.trimIndent() val selectionArgs = arrayOf(fileName, "application/pdf") val contentUri = MediaStore.Files.getContentUri("external") context.contentResolver.query( contentUri, projection, selection, selectionArgs, null )?.use { cursor -> if (cursor.moveToFirst()) { val id = cursor.getLong(cursor.getColumnIndexOrThrow(MediaStore.Files.FileColumns._ID)) return ContentUris.withAppendedId(contentUri, id) } } return null }
通知跳转Intent的关键配置
确保Intent正确授予临时读取权限,适配API33:
val pdfUri = getDownloadedPdfUri(context, yourDownloadId) ?: return val flags = if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) { PendingIntent.FLAG_UPDATE_CURRENT } else { PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT } val viewIntent = Intent(Intent.ACTION_VIEW).apply { setDataAndType(pdfUri, "application/pdf") addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) // API30+ 可选添加,确保权限覆盖Uri的前缀路径 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { addFlags(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION) } } // 创建PendingIntent并设置到通知 setContentIntent( PendingIntent.getActivity( context, 1, viewIntent, flags ) )
权限适配(Target SDK33)
- API23~32:需要在Manifest中声明
READ_EXTERNAL_STORAGE权限,并动态申请 - API33:对于PDF这类非媒体文件,仍需声明
READ_EXTERNAL_STORAGE权限并动态申请(READ_MEDIA_*权限仅针对图片、视频、音频)
Manifest声明:
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="33" />
为什么之前的方法失败?
- FileProvider:默认配置仅覆盖应用私有目录,若要适配公共Documents目录,需额外在
file_paths.xml中添加<external-path name="documents" path="Documents/" />,但该方式依赖文件路径的稳定性,不如MediaStore或DownloadManager查询可靠。 - 直接File转Uri:API29+会触发
FileUriExposedException,属于不安全的跨应用文件访问方式。
内容的提问来源于stack exchange,提问作者Gabriel Melo Cavalcante
相关产品推荐
相关产品推荐

