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

Widget点击打开Content Uri触发SecurityException问题求助

问题:Widget点击打开SAF选择的PDF触发安全异常

问题场景

  • 开发一款PDF Widget应用,核心流程:
    1. Widget配置阶段,通过SAF文件选择器让用户选择PDF文件,获取Content Uri(格式如content://com.android.providers.downloads.documents/document/msf%3A18)
    2. 将该Uri存入SharedPreferences持久化存储
    3. 用户点击Widget时,通过PendingIntent启动该Uri,调用第三方PDF阅读器打开文件
  • 实际测试时,点击Widget触发SecurityException,提示无权限访问目标Content Uri

相关代码

SAF文件选择代码

open class EAHActivity:AppCompatActivity(){

    private lateinit var arlPickFile: ActivityResultLauncher<Intent>
    private var callbackPickFile : (Uri) -> Unit = {}
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val requestType = ActivityResultContracts.StartActivityForResult()
        arlPickFile = registerForActivityResult(requestType){
            val result = it?.resultCode?:RESULT_CANCELED;
            if(result!= RESULT_OK) return@registerForActivityResult
            val data = it?.data?.data?:return@registerForActivityResult
            callbackPickFile.invoke(data)
            callbackPickFile = {}
        }
    }
    

    fun requestFile(category: String = Intent.CATEGORY_OPENABLE, filter : String = "application/pdf", onResult:(Uri)->Unit = {}) {
        callbackPickFile = onResult
        val baseIntent = Intent(Intent.ACTION_OPEN_DOCUMENT)
        baseIntent.addCategory(category)
        baseIntent.type = filter
        arlPickFile.launch(baseIntent)
    }
}

PendingIntent创建代码

fun getLaunchFilePendingIntent(path:String, ctx: Context):PendingIntent {
    Log.e("TAG", "path:$path" )

    val uri = Uri.parse(path)
    val openIntent = Intent(Intent.ACTION_VIEW)

    openIntent.setDataAndType(uri, "application/pdf")
    openIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
    
    val chooser = Intent.createChooser(openIntent, "Open with")
    val sdk31PlusFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0

    return PendingIntent.getActivity(ctx, 123, chooser, PendingIntent.FLAG_UPDATE_CURRENT or sdk31PlusFlag)
}

错误信息

java.lang.SecurityException: UID 10146 does not have permission to content://com.android.providers.downloads.documents/document/msf%3A18 [user 0]; you could obtain access using ACTION_OPEN_DOCUMENT or related APIs

解决方案

1. 持久化SAF Uri的访问权限

SAF返回的Uri默认是临时权限,仅在当前应用进程存活时有效。必须在获取Uri后调用takePersistableUriPermission将其转为持久权限,才能在应用重启、Widget触发时继续使用。

修改SAF回调代码:

arlPickFile = registerForActivityResult(requestType){
    val result = it?.resultCode?:RESULT_CANCELED;
    if(result!= RESULT_OK) return@registerForActivityResult
    val data = it?.data?.data?:return@registerForActivityResult
    
    // 添加持久化权限逻辑
    val takeFlags = Intent.FLAG_GRANT_READ_URI_PERMISSION
    contentResolver.takePersistableUriPermission(data, takeFlags)
    
    callbackPickFile.invoke(data)
    callbackPickFile = {}
}

2. 修正PendingIntent的权限传递问题

使用Intent.createChooser时,普通的FLAG_GRANT_READ_URI_PERMISSION权限不会自动传递给最终启动的第三方PDF阅读器,需要针对不同版本做适配:

适配Android 12+

使用Intent.FLAG_GRANT_PREFIX_URI_PERMISSION替代普通读权限标记,该标记会将权限传递给选择器启动的目标应用:

fun getLaunchFilePendingIntent(path:String, ctx: Context):PendingIntent {
    Log.e("TAG", "path:$path" )

    val uri = Uri.parse(path)
    val openIntent = Intent(Intent.ACTION_VIEW)

    openIntent.setDataAndType(uri, "application/pdf")
    openIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK 
    
    // 适配Android 12+的权限传递
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
        openIntent.flags = openIntent.flags or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
    } else {
        openIntent.flags = openIntent.flags or Intent.FLAG_GRANT_READ_URI_PERMISSION
    }
    
    val chooser = Intent.createChooser(openIntent, "Open with")
    val sdk31PlusFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0

    return PendingIntent.getActivity(ctx, 123, chooser, PendingIntent.FLAG_UPDATE_CURRENT or sdk31PlusFlag)
}

兼容低版本(Android 11及以下)

如果需要兼容低版本,可直接去掉createChooser,让系统自动弹出应用选择器,此时FLAG_GRANT_READ_URI_PERMISSION会正常传递:

fun getLaunchFilePendingIntent(path:String, ctx: Context):PendingIntent {
    Log.e("TAG", "path:$path" )

    val uri = Uri.parse(path)
    val openIntent = Intent(Intent.ACTION_VIEW)

    openIntent.setDataAndType(uri, "application/pdf")
    openIntent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
    
    val sdk31PlusFlag = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) PendingIntent.FLAG_MUTABLE else 0

    return PendingIntent.getActivity(ctx, 123, openIntent, PendingIntent.FLAG_UPDATE_CURRENT or sdk31PlusFlag)
}

核心原理

  • 持久化权限:SAF的临时权限会在应用进程销毁后失效,takePersistableUriPermission将权限转为持久化,允许应用在后续启动、Widget触发时继续访问该Uri。
  • 权限传递:createChooser会创建中间Intent,导致普通权限标记无法传递给目标应用,Android 12+的FLAG_GRANT_PREFIX_URI_PERMISSION专门解决了这个问题;低版本直接使用原始Intent即可让系统处理权限传递。

内容的提问来源于stack exchange,提问作者ansh sachdeva

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 21:25:07