安卓 - 文件提供者 - 权限拒绝

73

我有两个应用程序:app1和app2。

App2具有:

<provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="com.android.provider.ImageSharing"
        android:exported="false"
        android:grantUriPermissions="true" >
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/paths" />
</provider>

路径.xml :

<paths>

     <files-path name="my_images" path="images/"/>

</paths>

App2在其Activity中收到来自App1的请求,以获取一张图片的URI。一旦决定了URI,App2 Activity将执行以下操作:

Intent intent = new Intent();

intent.setDataAndType(contentUri, getContentResolver().getType(contentUri));

int uid = Binder.getCallingUid();
String callingPackage = getPackageManager().getNameForUid(uid);

getApplicationContext().grantUriPermission(callingPackage, contentUri,
                    Intent.FLAG_GRANT_READ_URI_PERMISSION);

setResult(Activity.RESULT_OK, intent);
finish();

App1在收到来自App2的结果后,执行以下操作:

Uri imageUri = data.getData();
if(imageUri != null) {
    ImageView iv = (ImageView) layoutView.findViewById(R.id.imageReceived);
    iv.setImageURI(imageUri);
}

在 App1 中,从 App2 返回时,我得到了以下异常:

java.lang.SecurityException:权限拒绝:从进程记录{52a99eb0 3493:com.android.App1.app/u0a57} (pid=3493, uid=10057) 打开未从 uid 10058 导出的提供程序 android.support.v4.content.FileProvider

我做错了什么?


请查看此链接:https://dev59.com/UmMl5IYBdhLWcg3woYPa?rq=1 - karan
你是否检查了callingPackage以确定它的值是否符合你的预期? - CommonsWare
@CommonsWare 好的,那似乎就是问题所在。那么我该怎么做呢?我需要将其分配给调用应用程序。谢谢! :) - Jake
1
@CommonsWare Binder.callingUid() 和 getPackageManager().getNameForUid(uid) 给我返回的是 App2 的包名,而不是 App1。 - Jake
你最终找到了安全异常的原因吗?当我发送意图时,也遇到了同样的异常。 - prostock
显示剩余2条评论
12个回答

72

结果证明,解决这个问题的唯一方法是授予所有可能需要它的软件包权限,如下所示:

List<ResolveInfo> resInfoList = context.getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY);
for (ResolveInfo resolveInfo : resInfoList) {
    String packageName = resolveInfo.activityInfo.packageName;
    context.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
}

3
这可能是适用于Android 5.0以下版本的正确解决方案。请参考该答案中的评论。 - Vince
1
适用于Android 8.0,谢谢!(附注:addFlags没有起作用) - Kirill Karmazin
2
这个黑客方法是唯一一个实际可行的解决方案(在浪费了数小时的时间后),这对于 Android 生态系统来说真的很丢脸! - doctorram

44

Android <= Lollipop (API 22)

有一篇由Lorenzo Quiroli撰写的优秀文章,解决了旧版Android的这个问题。

他发现您需要手动设置Intent的ClipData并为其设置权限,如下所示:

if ( Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP ) {
    takePictureIntent.setClipData( ClipData.newRawUri( "", photoURI ) );
    takePictureIntent.addFlags( Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION );
}

我在API 17上测试过,效果很好。无法找到其他有效的解决方案。


40
首先,我建议尝试切换到grantUriPermission()之外,直接在Intent本身上使用FLAG_GRANT_READ_URI_PERMISSION 通过addFlags()setFlag()
如果出现问题,您可以尝试将getCallingUid()逻辑移动到onCreate()中,而不是您当前的位置,并查看那里的实际“调用者”。

如果我通过ContentResolver.openInputStream()直接访问提供程序会怎样?调用应用程序中的grantUriPermission()会给我一个java.lang.SecurityException: Uid 10248 does not have permission to uri 0 @ content://...... - WindRider
@JaswanthManigundan:GitHub上的FileProvider示例使用了addFlags(),正如我的答案所建议的那样。 - CommonsWare
抱歉,我在你回答后立即删除了我的评论。我确实犯了一个错误,在addFlags之后添加了一个setFlags。非常抱歉并感谢! - Luis A. Florit
3
不适用于Android 8.0。唯一有帮助的方法是循环遍历所有resolveInfos并授予它们grantUriPermission()。这种方法有些野蛮。Google你真丢人。 - Kirill Karmazin
这种方式在Android 11上也不起作用。我们应该检查所有的resolveInfos。 - CodingBruceLee

26

这非常重要。我在另一个字符串额外参数中传递了Uri,这就是我的问题所在。 - Jarda Pavlíček

15

如何使用File Provider在Nougat上捕获照片。

要了解有关File Provider的内容,请访问以下链接: File Provider

对于KitKat和Marshmallow,请按照以下步骤进行操作。 首先,在MainfestFile中的application标签下添加ad tag provider。

 <provider
        android:name="android.support.v4.content.FileProvider"
        android:authorities="${applicationId}.provider"
        android:exported="false"
        android:grantUriPermissions="true">
        <meta-data
            android:name="android.support.FILE_PROVIDER_PATHS"
            android:resource="@xml/provider_paths"/>
    </provider>

在res文件夹下创建名为(provider_paths.xml)的文件

图片描述
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="external_files" path="."/>

我解决了这个问题,它出现在KitKat版本上。

private void takePicture() {
    Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
    if (takePictureIntent.resolveActivity(getPackageManager()) != null) {
        Uri photoURI = null;
        try {
            File photoFile = createImageFileWith();
            path = photoFile.getAbsolutePath();
            photoURI = FileProvider.getUriForFile(MainActivity.this,
                    getString(R.string.file_provider_authority),
                    photoFile);

        } catch (IOException ex) {
            Log.e("TakePicture", ex.getMessage());
        }
        takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI);
        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
            takePictureIntent.setClipData(ClipData.newRawUri("", photoURI));
            takePictureIntent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION|Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        startActivityForResult(takePictureIntent, PHOTO_REQUEST_CODE);
    }
}

  private File createImageFile() throws IOException {
    // Create an image file name
    String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.ENGLISH).format(new Date());
    String imageFileName = "JPEG_" + timeStamp + "_";
    File storageDir = new File(Environment.getExternalStoragePublicDirectory(
            Environment.DIRECTORY_DCIM), "Camera");
    File image = File.createTempFile(
            imageFileName,  /* prefix */
            ".jpg",         /* suffix */
            storageDir      /* directory */
    );

    // Save a file: path for use with ACTION_VIEW intents
    mCurrentPhotoPath = "file:" + image.getAbsolutePath();
    return image;
}

整天工作后,我发现这个解决方案适用于KitKat。 - Pawan Chaurasiya
这段代码在Android 4.4.2上崩溃,具体是在takePictureIntent.setClipData(ClipData.newRawUri("", photoURI));这一行。 - Stephen McCormick

3
我是这样解决问题的:
        Intent sIntent = new Intent("com.appname.ACTION_RETURN_FILE").setData(uri);
        List<ResolveInfo> resInfoList = activity.getPackageManager().queryIntentActivities(sIntent, PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo resolveInfo : resInfoList) {
            activity.grantUriPermission(FILE_PROVIDER_ID, uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        }
        sIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        activity.setResult(RESULT_OK, sIntent);

3

您需要设置特定包名的权限,然后才能访问它。

context.grantUriPermission("com.android.App1.app", fileUri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION | Intent.FLAG_GRANT_READ_URI_PERMISSION);

这是唯一一个对我有效的。 - Sam

2

感谢@CommonsWare提供的建议。

我的问题出在调用包上。由于某种原因,Binder.callingUid()getPackageManager().getNameForUid(uid)给出了App2而不是App1的包名。

我尝试在App2的onCreateonResume中调用它,但没有成功。

我使用以下方法解决了这个问题:

getApplicationContext().grantUriPermission(getCallingPackage(), 
          contentUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);

原来,该活动有专用的 API 可供此类操作。请参见此处

2

请确保每个应用程序的权限字符串都是唯一的。

我克隆了另一个使用相同权限字符串的应用程序,导致了错误。

File file = new File("whatever");
URI uri = FileProvider.getUriForFile(context, "unique authority string", file);

0
如果在新版本的Android上遇到此问题,请确保您传递给getUriForFile()的权限字符串与您在清单文件中声明的相同。

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