Caused by: java.lang.SecurityException: UID 10799 does not have permission to content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20210210_103348.jpg [user 0]
前段时间,项目组的一个小伙伴向我寻求了帮助。他负责的头像上传功能,在小米手机上突然无法走通,在某个阶段,系统打印出了以上错误日志。粗粗一看权限问题。根据线下实测,刚好在小米10上能复现问题,在小米11、华为手机上都没问题。第一反应感觉有点不可思议,按常理,一段代码如果不是系统版本问题,应该是全都有问题才对呀。如果是系统问题,那可就有点棘手了。
在解决问题前,首先介绍下业务场景。点击头像控件,跳转到系统相册选择图片,选择完毕跳转到系统裁剪程序裁剪图片。
跳转裁剪程序代码如下
fun gotoSystemCrop(uri: Uri) { val intent = Intent("com.android.camera.action.CROP") intent.setDataAndType(uri, "image/*") grantPermission(intent, uri) intent.putExtra("crop", "true");// 进行修剪 intent.putExtra("aspectX", 1) intent.putExtra("aspectY", 1) intent.putExtra("outputX", 360) intent.putExtra("outputY", 360) val tmpFile = File(externalCacheDir.toString() + File.separator + "photo" + "_" + System.currentTimeMillis() + ".png") var cropUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile( this, "com.peter.viewgrouptutorial1", tmpFile ) } else { Uri.fromFile(tmpFile) } intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri); grantPermission(intent, cropUri) startActivityForResult(intent, REQUEST_SYSTEM_CROP); }
请求授权代码如下
private fun grantPermission(intent: Intent, uri: Uri?) { var flag = Intent.FLAG_GRANT_READ_URI_PERMISSION flag = flag or Intent.FLAG_GRANT_WRITE_URI_PERMISSION intent.addFlags(flag) val resInfoList = packageManager .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) for (resolveInfo in resInfoList) { val packageName = resolveInfo.activityInfo.packageName println("xiaozhan $packageName") grantUriPermission(packageName, uri, flag) } }
首先
分析下gotoSystemCrop方法中的代码,调用了两次grantPermission方法,grantPermission(intent, uri)和grantPermission(intent, cropUri)。我们都知道Intent有显式调用和隐式调用两种,在此用的是隐式调用。后者可能会匹配到多个符合条件的Activity。uri表示图片选择程序返回给业务应用的图片路径,cropUri表示将裁剪后的图片保存到业务应用的存储路径。
其次
假设intent匹配到的程序包名叫com.miui.gallery
,我的业务app包名叫com.peter.viewgrouptutorial
。那么grantPermission(intent, uri)表示给com.miui.gallery
赋予读写uri的权限,而uri的主人正是com.miui.gallery
,而在Android中,App对自己暴露出去的Uri的读写权限是与生俱来的。所以第一个授权是不需要的。
再次
grantPermission(intent, cropUri) 表示在com.peter.viewgrouptutorial
程序中给com.miui.gallery
授予访问com.peter.viewgrouptutorial
暴露出来的Uri的权限。换句话说,就是让裁剪程序裁剪完成后,把图像保存到业务App中的存储路径中去。而跨App读写文件,是需要请求权限的。
上图我们需要明白一个道理
A应用能把UriA的读写权限赋予给B、C。而不能把UriB的权限赋予给C。
最后
说明下问题产生的原因。根据隐式Intent,系统匹配到了两个程序可以处理图片裁剪com.miui.gallery
和com.miui.mediaeditor
。
- 运行代码,在第一个grant处,业务应用把com.miui.gallery的uri读写权限授权给com.miui.mediaeditor,报错如下
java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { dat=content://com.miui.gallery.open/raw//storage/emulated/0/DCIM/Camera/IMG_20210210_103348.jpg typ=image/jpeg flg=0x1 }} to activity {com.peter.viewgrouptutorial/com.peter.viewgrouptutorial.PhotoActivity}: java.lang.SecurityException: UID 10799 does not have permission to content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20210210_103348.jpg [user 0] at android.app.ActivityThread.deliverResults(ActivityThread.java:4940) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4981) at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2046) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:225) at android.app.ActivityThread.main(ActivityThread.java:7564) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)
注释掉1处的授权代码
,运行报错如下,报错差不多。但是此时是ResolverActivity没有访问图片Uri的权限。
Process: com.peter.viewgrouptutorial, PID: 3894 java.lang.RuntimeException: Failure delivering result ResultInfo{who=null, request=1, result=-1, data=Intent { dat=content://com.miui.gallery.open/raw//storage/emulated/0/DCIM/Camera/IMG_20210210_103348.jpg typ=image/jpeg flg=0x1 }} to activity {com.peter.viewgrouptutorial/com.peter.viewgrouptutorial.PhotoActivity}: java.lang.SecurityException: UID 10799 does not have permission to content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20210210_103348.jpg [user 0] at android.app.ActivityThread.deliverResults(ActivityThread.java:4940) at android.app.ActivityThread.handleSendResult(ActivityThread.java:4981) at android.app.servertransaction.ActivityResultItem.execute(ActivityResultItem.java:51) at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:135) at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:95) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:2046) at android.os.Handler.dispatchMessage(Handler.java:107) at android.os.Looper.loop(Looper.java:225) at android.app.ActivityThread.main(ActivityThread.java:7564) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950) Caused
- 最后总结是,由于隐式启动裁剪程序,会调起应用选择界面ResolveActivity,而它没有访问Media程序图片Uri的权限。那么我们就要想办法把隐式调用转成显式调用。解决方案如下
fun gotoSystemCrop(uri: Uri) { val intent = Intent("com.android.camera.action.CROP") intent.setDataAndType(uri, "image/*") intent.putExtra("crop", "true");// 进行修剪 intent.putExtra("aspectX", 1) intent.putExtra("aspectY", 1) intent.putExtra("outputX", 360) intent.putExtra("outputY", 360) val tmpFile = File(externalCacheDir.toString() + File.separator + "photo" + "_" + System.currentTimeMillis() + ".png") var cropUri = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { FileProvider.getUriForFile( this, "com.peter.viewgrouptutorial1", tmpFile ) } else { Uri.fromFile(tmpFile) } intent.putExtra(MediaStore.EXTRA_OUTPUT, cropUri); grantPermissionFix(intent, cropUri) startActivityForResult(intent, REQUEST_SYSTEM_CROP); }
private fun grantPermissionFix(intent: Intent, uri: Uri?) { var flag = Intent.FLAG_GRANT_READ_URI_PERMISSION flag = flag or Intent.FLAG_GRANT_WRITE_URI_PERMISSION intent.addFlags(flag) val resInfoList = packageManager .queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY) for (resolveInfo in resInfoList) { val packageName = resolveInfo.activityInfo.packageName try { grantUriPermission(packageName, uri, flag) } catch (e: Exception) { continue } intent.action = null intent.component = ComponentName(resolveInfo.activityInfo.packageName, resolveInfo.activityInfo.name) break } }