【问题标题】:openInputStream cause SecurityException:Permission Denial (Android N+)openInputStream 导致 SecurityException:Permission Denial (Android N+)
【发布时间】:2023-10-08 04:15:01
【问题描述】:

我需要帮助解决这个问题,因为我是 Android 新手。

我的应用支持 JellyBean (16)Oreo (26)

我有一个 UploadService 需要 openInputStream() 上传数据,因为 Nougat 中的新行为。

此代码在 Marshmallow 及以下版本中运行良好,但在 Nougat 上总是给我SecurityException crash。它在使用 error 调用 openInputStream() 的行上崩溃:

java.lang.SecurityException: Permission Denial: 读取 com.miui.gallery.provider.GalleryOpenProvider uri content://com.miui.gallery.open/raw/%2Fstorage%2Femulated%2F0%2FDCIM%2FCamera%2FIMG_20171008_182834。 jpg from pid=30846, uid=10195 需要导出provider,或者grantUriPermission()

文件 uri 可能来自各种应用程序(图库、相机等)。我已将问题范围缩小到来自 ACTION_GET_CONTENT 意图的 uri(任何来自相机意图或 MediaRecorder 的东西都可以正常工作)。

我认为这是因为 uri 在传递到服务时失去了权限,但是添加 Intent.FLAG_GRANT_WRITE_URI_PERMISSIONIntent.FLAG_GRANT_READ_URI_PERMISSION 并没有帮助。

还尝试添加 FLAG_GRANT_PERSISTABLE_URI_PERMISSION 标志,但它仍然崩溃,getContentResolver().takePersistableUriPermission() 导致另一个 SecurityException 崩溃,说所述 uri 尚未被授予持久 uri...

UploadService.java

  //.......... code to prepare for upload

  if ( contentResolver != null && schemeContentFile ) {
            mMime = UtilMedia.getMime(this, uri);

            try {
                InputStream is     = contentResolver.openInputStream(uri);
                byte[]      mBytes = getBytes(is);

                Bundle fileDetail = UtilMedia.getFileDetailFromUri(this, uri);

                Log.d("AndroidRuntime", TAG + " " + mMime + " " + UtilToString.bundleToString(fileDetail) + " imageFile " + mFile);

                currTitle = fileDetail.getString(OpenableColumns.DISPLAY_NAME, "");
                MediaType type = MediaType.parse(mMime);
                requestFile = RequestBody.create(type, mBytes);

            } catch ( IOException e ) {
                e.printStackTrace();
            } catch ( SecurityException e ) {
                e.printStackTrace();
            }
        }

  //............continue to upload

提前谢谢你。

编辑(附加信息) 如果这很重要。调用服务的活动在将所有必需的数据发送到服务后调用finish(),让用户使用应用程序,同时在后台恢复上传(通知用户)。而且,上传是基于队列的,用户可以在活动中选择上传多个文件。第一个文件总是被上传,但之后的文件总是返回崩溃。

【问题讨论】:

  • 某些设备不允许从相机或图片目录读取文件的权限。如果从其他目录上传文件是否存在问题?
  • 感谢您的建议!我现在没有 Nougat 设备,但我会在有的时候尝试一下。哦,我可以得到mime就好了(该函数还使用了ContentResolver和相同的uri),是否意味着当时启用了读/写uri权限,但不知何故它需要其他权限才能直接读取文件?跨度>
  • 所以,我尝试了你的建议@NabinBhandari,但它仍然给了我同样的例外。我还编辑了我的帖子以添加更多信息以防万一。
  • 小米设备有这个问题。我不明白为什么从 Uri 读取的权限不像其他画廊提供者那样持久。就我而言,我试图从实际收到 Uri 的活动开始的活动中打开 Uri。使用标准的 Android 提供程序,它可以工作。 @NabinBhandari 有没有办法获得可以通过 GET_CONTENT 请求传播的权限?还是应该切换到 OPEN_DOCUMENT?根据文档Use ACTION_OPEN_DOCUMENT if you want your app to have long term, persistent access to documents owned by a document provider

标签: android permission-denied android-7.0-nougat securityexception


【解决方案1】:

我终于设法解决了这个问题。显然它 因为给定 uri 的权限仅在接收活动处于活动状态时才有效。因此,将 uri 发送到后台服务(上传服务)将按预期生成 SecurityException,除非 uri 是可持久化的 uri(即来自 ACTION_OPEN_DOCUMENT)。

所以我的解决方案是将文件复制到我的应用程序创建的文件中,并使用FileProvider.getUriForFile() 函数获取 uri 并将其发送到后台服务,并在我的服务完成上传后删除副本。即使在调用活动完成后,这也能正常工作。

【讨论】:

    【解决方案2】:

    以下代码在我的设备(android 7)中运行良好:

    public void pickImage(View view) {
        try {
            Intent photoPickerIntent = new Intent(Intent.ACTION_GET_CONTENT);
            photoPickerIntent.setType("image/*");
            photoPickerIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
            startActivityForResult(photoPickerIntent, RC_PICK_IMAGE);
        } catch (ActivityNotFoundException e) {
            e.printStackTrace();
        }
    }
    

    onActivityResult:

    Uri uri = data.getData();
    InputStream in = getContentResolver().openInputStream(uri);
    

    【讨论】:

    • 嗨,所以它在活动结果时有效。但我需要将 uri 从活动发送到 UploadService(后台服务)。奇怪的是,它只对第一个项目成功。将我的 ACTION_GET_CONTENT 更改为 ACTION_OPEN_DOCUMENT 并具有持久的 uri 权限,并且它在所有发送的 uri 的服务中都可以正常工作,因此它肯定与 uri 本身或 uri 的权限有关。如何将此权限传递给服务?正如我在问题中所说,我已经将读/写 uri 标志添加到服务的意图中,但它什么也没做......谢谢。
    • 我不确定这是否是个好主意,但您可以将 Uri 转换为路径,并将路径传递给您的服务。
    • 如果我从 ACTION_GET_CONTENT 发送内容 uri 的路径(去过那里),Nougat 将引发另一个异常。但也许,如果我将文件 onActivityResult() 复制到我的应用程序控制的文件中并改用该文件 uri/path,它会工作,因为相机意图中的 uri 工作正常。我要试试这个并在这里发布结果。谢谢:)