You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android Nougat+跨应用共享公共文件的正确实现方案咨询

解决Android 7.0+公共文件分享的content:// URI困境

作为文件管理类应用开发者,我太懂你这个痛点了——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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.26 09:18:27