【问题标题】:Android - file provider - permission denialAndroid - 文件提供者 - 权限拒绝
【发布时间】:2014-08-19 12:00:57
【问题描述】:

我有两个应用程序: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>

paths.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();

从 App2 收到结果后,App1 执行以下操作:

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

在 App1 中,从 App2 返回时,出现以下异常:

java.lang.SecurityException: Permission Denial: opening 提供者 android.support.v4.content.FileProvider 来自 ProcessRecord{52a99eb0 3493:com.android.App1.app/u0a57} (pid=3493, uid=10057) 不是从 uid 10058 导出的

我做错了什么?

【问题讨论】:

  • @KaranMer 但我正在遵循这些步骤.. :(
  • 您检查过callingPackage 是否是您期望的值?
  • @CommonsWare OK .. 这似乎是问题所在。那么我该怎么做呢?我需要将其分配给调用应用程序。谢谢 ! :)
  • @CommonsWare Binder.callingUid() 和 getPackageManager().getNameForUid(uid) 给我的是 App2 的包名而不是 App1

标签: android android-fileprovider


【解决方案1】:

原来解决这个问题的唯一方法是向所有可能需要它的包授予权限,如下所示:

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);
}

【讨论】:

  • 这对于棒棒糖之前的版本来说可能是正确的解决方案。在that answer 上查看 cmets
  • 适用于 Android 8.0,谢谢! (ps addFlags 没有成功)
  • 这个 hack 是唯一有效的解决方案(在浪费了很多时间之后),这对于 Android 生态系统来说真是可耻!
【解决方案2】:

Android

Lorenzo Quiroli 的 great article 解决了旧 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 上对此进行了测试,效果很好。在任何地方都找不到有效的解决方案。

【讨论】:

    【解决方案3】:

    首先,我会尝试从 grantUriPermission() 切换到 FLAG_GRANT_READ_URI_PERMISSION 本身 Intent via addFlags()setFlag()

    如果由于某种原因不起作用,您可以尝试将您的 getCallingUid() 逻辑移动到 onCreate() 而不是您拥有的任何地方,看看您是否可以在那里找到实际的“调用者”。

    【讨论】:

    • 如果我直接通过ContentResolver.openInputStream() 访问提供程序怎么办?调用应用程序中的grantUriPermission() 给了我java.lang.SecurityException: Uid 10248 does not have permission to uri 0 @ content://......
    • @JaswanthManigundan:This FileProvider example in GitHub 使用 addFlags(),正如我的回答所暗示的那样。
    • 抱歉,在您发布答案后立即删除了我的评论。我确实犯了一个错误,在addFlags 之后添加了setFlags。对不起,谢谢!
    • 不适用于 Android 8.0。唯一有帮助的是循环遍历所有的 resolveInfos 和 grantUriPermission()。多么野蛮的做法。真丢脸,谷歌
    【解决方案4】:

    只需添加 setData(contentUri); 和 根据需求添加 addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);

    这解决了 java.lang.SecurityException: Permission Denial

    已验证。

    这是按照 https://developer.android.com/reference/android/support/v4/content/FileProvider.html#Permissions

    【讨论】:

    • 这真的很重要。我确实在另一个额外的字符串中传递了 Uri,这就是我的问题的原因
    【解决方案5】:

    如何使用 File Provider 在 Nougat 上使用相机拍摄图像。

    要了解有关文件提供程序的信息,请点击此链接 File Provider

    ,kit kat 和 marshmallow 遵循这些步骤。 首先是 MainfestFile 中 application 标签下的广告标签提供程序。

     <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="."/>
    

    我已经解决了这个问题,它来自 kit 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。
    • 这在 Android 4.4.2 上的 takePictureIntent.setClipData(ClipData.newRawUri("", photoURI)); 处崩溃;
    【解决方案6】:

    我这样解决了问题:

            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);
    

    【讨论】:

      【解决方案7】:

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

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

      【讨论】:

        【解决方案8】:

        感谢@CommonsWare 的建议。

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

        我尝试在 App2 的 onCreateonResume 中调用它,但没有任何乐趣。

        我使用以下方法来解决它:

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

        事实证明,Activity 有专门的 API。见here

        【讨论】:

          【解决方案9】:

          确保每个应用程序的权限字符串都是唯一的。 我克隆了另一个具有相同权限字符串的应用程序,导致错误

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

          【讨论】:

            【解决方案10】:

            在我的情况下,我要与文本共享图片,解决方案是添加两个标志,阅读 FLAG_GRANT_READ_URI_PERMISSION 并编写 FLAG_GRANT_WRITE_URI_PERMISSION,如下所示:

                    Intent intent = new Intent(android.content.Intent.ACTION_SEND);
                intent.putExtra(Intent.EXTRA_STREAM, uri);
                intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
                intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
                intent.setType("image/png");
                intent.putExtra(android.content.Intent.EXTRA_SUBJECT, getString(R.string.txtShareAppTitle));
                intent.putExtra(Intent.EXTRA_TEXT, getString(R.string.txtShareApp) ;
                startActivity(intent);
            

            【讨论】:

              【解决方案11】:

              我是这样理解的

              try{
                  Intent(MediaStore.ACTION_IMAGE_CAPTURE).also { takePictureIntent ->
                      takePictureIntent.resolveActivity(packageManager)?.also {
                              val photoFile: File? = try {
                                  createImageFile()
                              } catch (ex: IOException) {
                                  null
                              }
                          photoFile?.also {
                              val photoURI: Uri = FileProvider.getUriForFile(
                                  this,
                                  "$packageName.fileprovider",
                                  it
                              )
                                  if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP) {
                                      val permissionTemp =  Intent.FLAG_GRANT_READ_URI_PERMISSION and Intent.FLAG_GRANT_WRITE_URI_PERMISSION
                                      grantUriPermission(packageName, Uri.fromFile(photoFile), permissionTemp)
                                      takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, Uri.fromFile(photoFile))
                                  }else{
                                      takePictureIntent.putExtra(MediaStore.EXTRA_OUTPUT, photoURI)
                                  }
                              startActivityForResult(takePictureIntent, REQUEST_IMAGE_CAPTURE)
                          }
                      }
                  }
              }
                  catch (ex: IOException){
                  Log.d("IOException", "${ex.printStackTrace()}")
              }
              

              file_provider_paths.xml

              <paths>
                 <external-files-path name="my_images" path="/" />
                 <external-path name="my_images" path="/"/>
                 <external-path name="external" path="." />
                 <external-files-path name="external_files" path="." />
                 <files-path name="files" path="." />
              </paths>
              

              然后按照文档进行操作 Take photos

              【讨论】:

                【解决方案12】:

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

                【讨论】:

                  猜你喜欢
                  • 2018-02-04
                  • 2016-08-29
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-06-04
                  相关资源
                  最近更新 更多