解决了一个大家都有可能遇到的奇葩权限问题

简介: 解决了一个大家都有可能遇到的奇葩权限问题

Google搜索该bug,几乎找不到解决该问题的答案(也有可能是我没找到),特此记录一下


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读写文件,是需要请求权限的。

640.png

上图我们需要明白一个道理 A应用能把UriA的读写权限赋予给B、C。A应用不能把UriB的权限赋予给C。


最后 说明下问题产生的原因。根据隐式Intent,系统匹配到了两个程序可以处理图片裁剪com.miui.gallerycom.miui.mediaeditor。奇葩的手机


  1. 运行代码,在第一个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
      }
  }
相关文章
|
编解码 Linux Android开发
安卓投屏神器 Scrcpy 安装与使用(支持 Mac、Windows、Linux)
安卓投屏神器 Scrcpy 安装与使用(支持 Mac、Windows、Linux)
37414 1
|
Java Maven Android开发
Android 阿里云镜像整理
Android 阿里云镜像整理
7364 0
|
监控 Java Shell
基于python+uiautomator2,2020.12月最新库的使用方法,更新watcher使用方法(三)
WatchContext,目前的这个watch_context是用threading启动的,每2s检查一次 目前还只有click这一种触发操作
1670 0
|
Android开发
Android中如何快速的实现RecycleView的拖动重排序功能
使用`ItemTouchHelper`和自定义`Callback`,在`RecyclerView`中实现拖动排序功能。定义`ItemTouchHelperAdapter`接口,`Adapter`实现它以处理`onItemMove`方法。`SimpleItemTouchHelperCallback`设置拖动标志,如`LEFT`或`RIGHT`(水平拖动),并绑定到`RecyclerView`以启用拖动。完成这些步骤后,即可实现拖放排序。关注公众号“AntDream”获取更多内容。
279 3
|
Java Android开发 数据安全/隐私保护
Android中多进程通信有几种方式?需要注意哪些问题?
本文介绍了Android中的多进程通信(IPC),探讨了IPC的重要性及其实现方式,如Intent、Binder、AIDL等,并通过一个使用Binder机制的示例详细说明了其实现过程。
791 4
|
数据可视化 API 数据库
低代码/无代码运动:软件开发的未来还是乌托邦?
低代码/无代码(LCNC)平台近年来在软件开发领域引起广泛关注,通过简化界面和预构建模块,使非技术用户也能快速构建应用。本文探讨其潜在影响、优势与挑战,以及对传统开发的影响。核心优势包括快速开发、易于使用和成本效益;主要挑战则涉及定制化限制、性能问题和技术锁定。LCNC平台促使开发者角色转变,促进业务与IT融合,并加速创新。尽管优势明显,但其局限性意味着不会完全取代传统开发,而是成为重要工具之一。
|
Android开发 开发者 索引
Android实战经验之如何使用DiffUtil提升RecyclerView的刷新性能
本文介绍如何使用 `DiffUtil` 实现 `RecyclerView` 数据集的高效更新,避免不必要的全局刷新,尤其适用于处理大量数据场景。通过定义 `DiffUtil.Callback`、计算差异并应用到适配器,可以显著提升性能。同时,文章还列举了常见错误及原因,帮助开发者避免陷阱。
921 9
|
9月前
|
JavaScript Java 测试技术
基于Java+SpringBoot+Vue实现的车辆充电桩系统设计与实现(系统源码+文档+部署讲解等)
面向大学生毕业选题、开题、任务书、程序设计开发、论文辅导提供一站式服务。主要服务:程序设计开发、代码修改、成品部署、支持定制、论文辅导,助力毕设!
|
Android开发
DialogFragment 使用指南:几个小问题的解法
DialogFragment是Android中用于创建弹窗的特殊Fragment,继承自Fragment。使用步骤包括:1. 创建子类,2. 在onCreateView加载布局,3. onViewCreated初始化控件,4. 通过show方法显示。示例代码展示了一个基本的DialogFragment及其布局。此外,文中还解答了三个常见问题:如何设置弹窗宽度为match_parent,如何使弹窗位于屏幕底部,以及如何去除弹窗四周的默认padding。每个问题都提供了相应的解决方案,涉及在onStart中调整窗口参数和设置自定义样式。
1459 2
DialogFragment 使用指南:几个小问题的解法
|
安全 Java API
【Java】已解决java.lang.SecurityException异常
【Java】已解决java.lang.SecurityException异常
1936 0