Android Nougat+跨应用共享公共文件的正确实现方案咨询
作为文件管理类应用开发者,我太懂你这个痛点了——Android 7.0开始强制用content://替代file://跨应用分享,这套临时权限机制对私有文件合理,但对公共存储里的文件(比如/sdcard/Downloads/kittens.png)完全是脱裤子放屁,明明所有应用都该有权限访问,却非要走临时授权那套弯路。
下面给你几个实用的解决方案,完全贴合你的场景:
1. 利用系统MediaStore获取永久有效的content:// URI
公共存储里的系统目录(Downloads、Pictures、Movies等)里的文件,本身就在MediaStore的系统索引中,我们可以直接查询对应的content:// URI——这种URI是系统级的,其他应用只要有基础的存储读取权限就能访问,不需要临时授权,完美匹配你的需求。
代码实现(分版本兼容)
Android 9及以下
可以直接通过文件路径查询:
fun getPublicFileContentUri(context: Context, file: File): Uri? { val filePath = file.absolutePath val cursor = context.contentResolver.query( MediaStore.Downloads.EXTERNAL_CONTENT_URI, // 对应文件所在的目录,比如Pictures就用MediaStore.Images.Media.EXTERNAL_CONTENT_URI arrayOf(MediaStore.Downloads._ID), "${MediaStore.Downloads.DATA} = ?", arrayOf(filePath), null ) return cursor?.use { if (it.moveToFirst()) { val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Downloads._ID)) ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id) } else { null } } }
Android 10及以上(Scoped Storage)
MediaStore.DATA字段被废弃了,改用文件名和相对路径查询:
@RequiresApi(Build.VERSION_CODES.Q) fun getPublicFileContentUriQ(context: Context, file: File): Uri? { val fileName = file.name // 对应目录的相对路径,比如Downloads是"Download/",Pictures是"Pictures/" val relativePath = "${Environment.DIRECTORY_DOWNLOADS}/" val cursor = context.contentResolver.query( MediaStore.Downloads.EXTERNAL_CONTENT_URI, arrayOf(MediaStore.Downloads._ID), "${MediaStore.Downloads.DISPLAY_NAME} = ? AND ${MediaStore.Downloads.RELATIVE_PATH} = ?", arrayOf(fileName, relativePath), null ) return cursor?.use { if (it.moveToFirst()) { val id = it.getLong(it.getColumnIndexOrThrow(MediaStore.Downloads._ID)) ContentUris.withAppendedId(MediaStore.Downloads.EXTERNAL_CONTENT_URI, id) } else { null } } }
处理未被索引的文件
如果是用户刚下载/创建的文件,MediaStore还没同步索引,先发送广播让系统扫描:
fun scanFileToMediaStore(context: Context, file: File) { val scanIntent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) scanIntent.data = Uri.fromFile(file) context.sendBroadcast(scanIntent) }
扫描完成后再查询就能拿到URI了。
2. Android 11+:使用MANAGE_EXTERNAL_STORAGE权限(谨慎使用)
如果你的应用需要访问自定义公共目录(非系统默认的Downloads等),可以申请MANAGE_EXTERNAL_STORAGE权限——这个权限能让你的应用完整访问公共存储,之后你可以通过MediaStore查询任意公共文件的content URI,或者甚至用FileProvider生成自定义的content URI(不过还是MediaStore的方式更省心)。
注意:这个权限属于敏感权限,Google Play审核会严格限制使用场景,只有真正的文件管理类应用才会通过审核,申请时要在权限说明里明确说明用途。
3. 避免踩坑:别用FileProvider的临时权限
FileProvider的grantUriPermission()生成的URI是临时授权,重启后就失效了,完全不符合公共文件永久可访问的需求,所以直接放弃这种方案就好。
最后提醒下,Android 13+的权限更细分了,比如访问Downloads需要READ_MEDIA_IMAGES/READ_MEDIA_VIDEO/READ_MEDIA_AUDIO或者READ_EXTERNAL_STORAGE(兼容旧版本),记得按需申请对应的权限。
内容的提问来源于stack exchange,提问作者TiberiumFusion

