【问题标题】:java.lang.SecurityException: Permission Denial: opening providerjava.lang.SecurityException:权限拒绝:打开提供程序
【发布时间】:2018-12-20 01:35:26
【问题描述】:

我使用以下方法启动图像选择器意图:

final Intent pickIntent = new Intent(Intent.ACTION_PICK, MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
startActivityForResult(intent, PICK_IMAGE);

onActivityResult() 中,我得到所有挑选的图像的uris 并启动在后台运行的作业并上传这些图像(https://github.com/yigit/android-priority-jobqueue)。但是,如果我按下后退按钮并退出活动,那么任何未启动的作业在运行时都无法访问拾取的图像并引发异常:

java.lang.SecurityException: Permission Denial: 从未从 uid 10123 导出的 ProcessRecord{...} (pid=2407, uid=10117) 打开提供程序 com.google.android.apps.photos.contentprovider.MediaContentProvider

它发生的原因是因为一旦活动完成,权限就会被撤销。根据文档https://developer.android.com/guide/topics/providers/content-provider-basics.html

这些是特定内容 URI 的权限,持续到接收它们的活动完成。

我的问题是,有解决方法吗?比如在应用程序级别获得许可之类的?

有什么替代方法可以解决这个问题?一种快速的解决方案似乎是复制所有挑选的图像,然后上传它们,但这似乎是最后的手段。

【问题讨论】:

    标签: android android-contentprovider android-permissions permission-denied


    【解决方案1】:

    注意:尝试从 uri 获取文件名是错误的。不要这样做!内容提供者还可以共享任何文件中不存在的任意数据,或者文件名可能会产生误导。例如。 content://downloads/some_secret_data 可能指向不在downloads 文件夹中的文件。

    所以最好的办法是立即从内容提供者读取/复制数据,然后使用该数据做任何你想做的事情。就我而言,我正在上传它。


    以前的错误答案(不要这样做!):

    这就是我所做的,对我来说效果很好。如果有人有更好的解决方案,请分享。 我有android.permission.READ_EXTERNAL_STORAGE,所以当用户选择图像时,我使用具体的文件路径而不是uris 返回的onActivityResult()。为了检索文件路径,我使用了这个方便的类https://stackoverflow.com/a/20559175/826606 瞧!

    【讨论】:

      【解决方案2】:

      我通过以下代码解决了这个问题: 当我们从谷歌照片应用程序获取图像时,我们需要为图像提供 realPath

      //get Path
      @TargetApi(Build.VERSION_CODES.KITKAT)
      public String getRealPathFromURI(final Uri uri) {
          final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
      
          // DocumentProvider
          if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
              // ExternalStorageProvider
              if (isExternalStorageDocument(uri)) {
                  final String docId = DocumentsContract.getDocumentId(uri);
                  final String[] split = docId.split(":");
                  final String type = split[0];
      
                  if ("primary".equalsIgnoreCase(type)) {
                      return Environment.getExternalStorageDirectory() + "/" + split[1];
                  }
              }
              // DownloadsProvider
              else if (isDownloadsDocument(uri)) {
                  final String id = DocumentsContract.getDocumentId(uri);
                  final Uri contentUri = ContentUris.withAppendedId(
                          Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
      
                  return getDataColumn(contentUri, null, null);
              }
              // MediaProvider
              else if (isMediaDocument(uri)) {
                  final String docId = DocumentsContract.getDocumentId(uri);
                  final String[] split = docId.split(":");
                  final String type = split[0];
      
                  Uri contentUri = null;
                  if ("image".equals(type)) {
                      contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
                  } else if ("video".equals(type)) {
                      contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
                  } else if ("audio".equals(type)) {
                      contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
                  }
      
                  final String selection = "_id=?";
                  final String[] selectionArgs = new String[]{
                          split[1]
                  };
      
                  return getDataColumn(contentUri, selection, selectionArgs);
              }
          }
          // MediaStore (and general)
          else if ("content".equalsIgnoreCase(uri.getScheme())) {
              // Return the remote address
              if (isGooglePhotosUri(uri))
                  return uri.getLastPathSegment();
      
              return getDataColumn(uri, null, null);
          }
          // File
          else if ("file".equalsIgnoreCase(uri.getScheme())) {
              return uri.getPath();
          } else
              return getRealPathFromURIDB(uri);
      
          return null;
      }
      
      /**
       * Gets real path from uri.
       *
       * @param contentUri the content uri
       * @return the real path from uri
       */
      private String getRealPathFromURIDB(Uri contentUri) {
          Cursor cursor = context.getContentResolver().query(contentUri, null, null, null, null);
          if (cursor == null) {
              return contentUri.getPath();
          } else {
              cursor.moveToFirst();
              int index = cursor.getColumnIndex(MediaStore.Images.ImageColumns.DATA);
              String realPath = cursor.getString(index);
              cursor.close();
              return realPath;
          }
      }
      
      /**
       * Gets data column.
       *
       * @param uri           the uri
       * @param selection     the selection
       * @param selectionArgs the selection args
       * @return the data column
       */
      public String getDataColumn(Uri uri, String selection,
                                         String[] selectionArgs) {
          Cursor cursor = null;
          final String column = "_data";
          final String[] projection = {
                  column
          };
      
          try {
              cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
                      null);
              if (cursor != null && cursor.moveToFirst()) {
                  final int index = cursor.getColumnIndexOrThrow(column);
                  return cursor.getString(index);
              }
          } finally {
              if (cursor != null)
                  cursor.close();
          }
          return null;
      }
      
      /**
       * Is external storage document boolean.
       *
       * @param uri The Uri to check.
       * @return Whether the Uri authority is ExternalStorageProvider.
       */
      public boolean isExternalStorageDocument(Uri uri) {
          return "com.android.externalstorage.documents".equals(uri.getAuthority());
      }
      
      /**
       * Is downloads document boolean.
       *
       * @param uri The Uri to check.
       * @return Whether the Uri authority is DownloadsProvider.
       */
      public boolean isDownloadsDocument(Uri uri) {
          return "com.android.providers.downloads.documents".equals(uri.getAuthority());
      }
      
      /**
       * Is media document boolean.
       *
       * @param uri The Uri to check.
       * @return Whether the Uri authority is MediaProvider.
       */
      public boolean isMediaDocument(Uri uri) {
          return "com.android.providers.media.documents".equals(uri.getAuthority());
      }
      
      /**
       * Is google photos uri boolean.
       *
       * @param uri The Uri to check.
       * @return Whether the Uri authority is Google Photos.
       */
      public boolean isGooglePhotosUri(Uri uri) {
          return "com.google.android.apps.photos.content".equals(uri.getAuthority());
      }
      

      现在在 onActivityResult 方法中:

         @Override
         public void onActivityResult(int requestCode, int resultCode, Intent data) {
          super.onActivityResult(requestCode, resultCode, data);
          if (resultCode == Activity.RESULT_OK) {
              if (requestCode == ChoosePhoto.CHOOSE_PHOTO_INTENT) {
                  if (data != null && data.getData() != null) {
                      handleGalleryResult(data);
                  } else {
                      handleCameraResult(choosePhoto.getCameraUri());
                  }
              } else if (requestCode == ChoosePhoto.SELECTED_IMG_CROP) {
                  mImgProfile.setImageURI(choosePhoto.getCropImageUrl());
              }
          }
      }
      

      现在在 handleGalleryResult 方法中:

      public void handleGalleryResult(Intent data) {
          try {
              cropPictureUrl = Uri.fromFile(createImageTempFile(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES)));
              String realPathFromURI = FileUtil.getRealPathFromURI(data.getData());
              File file = new File(realPathFromURI);
              if(file.exists()) {
                  if(currentAndroidDeviceVersion>23){
                      cropImage(FileProvider.getUriForFile(mContext, mContext.getApplicationContext().getPackageName() + ".provider", file), cropPictureUrl);
                  }else{
                      cropImage(Uri.fromFile(file), cropPictureUrl);
                  }
      
              } else
                  cropImage(data.getData(), cropPictureUrl);
          } catch (IOException e) {
              e.printStackTrace();
          }
      }
      

      创建图像临时文件:

      @SuppressLint("SimpleDateFormat")
      public File createImageTempFile(File filePathDir) throws IOException {
          String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss").format(new Date());
      
          String imageFileName = "JPEG_" + timeStamp + "_";
          return File.createTempFile(
                  imageFileName,  /* prefix */
                  ".jpg",         /* suffix */
                  filePathDir      /* directory */
          );
      }
      

      【讨论】:

      • 不要这样获取图片文件路径,错了。内容提供者不需要在 uri 中显示文件路径。所以你可能有一个content://xyz 形式的uri,它指向content://downloads/public_downloads/somefile.png,或者根本不指向任何文件。它可能只是一个内存位图。所以不要这样做。而是立即读取数据。
      • 当 id 为 raw 时,else if (isDownloadsDocument(uri)) 中的 final Uri contentUri = ContentUris.withAppendedId(Uri.parse("content://downloads/public_downloads"), Long.valueOf(id)); 行会抛出数字格式异常:/storage/emulated/0/Download/124.jpg 在模拟器上。
      【解决方案3】:

      对于这个问题,有比关于读取/复制的公认答案更好的解决方案。

      基本上,您必须使用不同的意图并请求永久 Uri 权限,该权限在您重新启动后仍然存在。

      解决办法在这里:Permission for an image from Gallery is lost after re-launch

      【讨论】:

        猜你喜欢
        • 2019-07-02
        • 1970-01-01
        • 1970-01-01
        • 2018-10-28
        • 2015-11-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多