打开InputStream引起安全异常:权限拒绝(Android N+)

4

由于我是Android新手,所以需要您的帮助解决这个问题。

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

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

这段代码在Marshmallow及以下版本中运行良好,但在Nougat上总是给我SecurityException崩溃,并在调用openInputStream()的那一行崩溃,出现错误

java.lang.SecurityException: Permission Denial: reading 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 requires the provider be exported, or 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(),让用户使用应用程序,而上传在后台恢复(通过通知告诉用户)。此外,上传基于队列,并且用户可以选择在活动中上传多个文件。第一个文件总是会被上传,但之后的文件总是崩溃返回。


有些设备不允许从相机或图片目录读取文件的权限。如果您从不同的目录上传文件,问题是否仍然存在? - Nabin Bhandari
谢谢您的建议!我现在没有Nougat设备,但是我会在有机会时尝试一下。哦,我可以很好地获取mime(该函数也使用ContentResolver和相同的uri),这是否意味着读/写uri权限已启用,但某种方式需要其他权限才能直接读取文件? - ayakashi-ya
所以,我尝试了你的建议@NabinBhandari,但它仍然给我相同的异常。我还编辑了我的帖子,以防万一添加更多信息。 - ayakashi-ya
我在小米设备上遇到了这个问题。我不明白为什么与其他图库提供程序不同,从Uri读取的权限不是持久的。在我的情况下,我正在尝试从由实际接收Uri的活动启动的活动中打开Uri。使用标准的Android提供程序可以正常工作。@NabinBhandari是否有任何方法可以获得可以通过GET_CONTENT请求传播的权限?还是应该切换到OPEN_DOCUMENT?根据文档,如果您希望应用程序长期持久地访问文档提供程序拥有的文档,请使用ACTION_OPEN_DOCUMENT。 - androidguy
2个回答

11

我终于成功修复了它。显然这是因为给定 uri 的权限只在接收 activity 处于活动状态时才有效。因此,向后台服务(上传服务)发送 uri 将会导致预期的 SecurityException 异常,除非 uri 是可持久化 uri (即来自 ACTION_OPEN_DOCUMENT)。

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


1
以下代码在我的设备(安卓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);

嗨,当在activityresult时它可以工作。但我需要从活动向UploadService(后台服务)发送uri。仅对第一项成功,奇怪的是。将我的ACTION_GET_CONTENT更改为具有可持久化uri权限的ACTION_OPEN_DOCUMENT,并且在服务中为所有发送的uri正常工作,因此肯定与uri本身或uri的权限有关。如何将此权限传递给服务?正如我在问题中所述,我已经将读/写uri标志添加到意图以提供服务,但什么也没做...谢谢。 - ayakashi-ya
我不确定这是否是一个好主意,但你可以将Uri转换为路径,并将路径传递给你的服务。 - Nabin Bhandari
如果我从ACTION_GET_CONTENT发送内容uri的路径,Nougat会抛出另一个异常(已经遇到过)。但是也许,如果我将文件onActivityResult()复制到我的应用程序控制的文件中,并使用该文件uri/path,那么它可能会起作用,因为来自相机意图的uri可以正常工作。我将尝试这个方法并在这里发布结果。谢谢 :) - ayakashi-ya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接