Android通过ContentProvider传递图片URI遇权限异常求解决方案
问题
我希望通过ContentProvider以Cursor行数据形式向其他App传递图片文件,在自定义ContentProvider的query方法中生成FileProvider的图片Content URI并存入Cursor返回,相关代码如下:
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { val cursor = MatrixCursor(arrayOf("id", "title", "image")) val movie = getMovie() val rowBuilder = cursor.newRow() rowBuilder.add("id", movie.id) rowBuilder.add("title", movie.title) val fileUri = FileProvider.getUriForFile(context!!, "com.my.content.provider.fileProvider", movie.imageFile) rowBuilder.add("image", fileUri.toString()) return cursor }
FileProvider的配置如下:
<provider android:name="androidx.core.content.FileProvider" android:authorities="com.my.content.provider.fileProvider" android:enabled="true" android:exported="false" android:grantUriPermissions="true"> <meta-data android:name="android.support.FILE_PROVIDER_PATHS" android:resource="@xml/file_paths" /> </provider>
但其他App查询该ContentProvider获取到图片URI后,调用contentResolver.openInputStream访问时触发SecurityException:Permission Denial: opening provider androidx.core.content.FileProvider that is not exported from UID 10437。其他App的调用代码如下:
val cursor: Cursor? = contentResolver.query( Uri.parse("content://com.my.content.provider/movies/1"), null, null, null, null ) val image = cursor.getString(cursor.getColumnIndexOrThrow("image")) // 获取图片URI成功 val inStream = contentResolver.openInputStream(Uri.parse(image)) // 抛出异常 val bitmap = BitmapFactory.decodeStream(inStream)
请问如何为该URI授予读取权限?目前仅找到通过Intent共享Content URI的示例,是否可以通过ContentProvider传递此类URI?
解决方案
1. 在自定义ContentProvider中手动授予URI权限
你的FileProvider已经配置了grantUriPermissions="true",可以在query方法中直接给请求方App授予读取权限。修改后的query方法如下:
override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? { val cursor = MatrixCursor(arrayOf("id", "title", "image")) val movie = getMovie() val rowBuilder = cursor.newRow() rowBuilder.add("id", movie.id) rowBuilder.add("title", movie.title) val fileUri = FileProvider.getUriForFile(context!!, "com.my.content.provider.fileProvider", movie.imageFile) rowBuilder.add("image", fileUri.toString()) // 获取请求方包名,授予读取权限 val callingPackage = context.callingPackage callingPackage?.let { context.grantUriPermission( it, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION ) } return cursor }
2. 可选:配置持久化权限(如需长期访问)
如果需要让权限在Cursor关闭后仍能生效,可添加持久化权限标记。在自定义ContentProvider的权限授予代码中修改:
context.grantUriPermission( callingPackage, fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION )
然后在调用方App中,获取URI后调用:
contentResolver.takePersistableUriPermission( Uri.parse(image), Intent.FLAG_GRANT_READ_URI_PERMISSION )
问题根源说明
FileProvider默认exported="false",不会对外开放访问权限。通过Intent共享URI时,系统会自动处理权限授予,但直接通过Cursor传递URI时,系统不会自动授权,必须手动调用grantUriPermission给请求方App赋予对应权限。
ContentProvider传递URI的可行性
完全可以通过ContentProvider传递FileProvider的URI,核心是确保请求方App拥有访问该URI的权限,手动完成权限授予即可实现。
内容的提问来源于stack exchange,提问作者Ashwin

