正在将图像从 Android 上传到我的服务器(权限被拒绝)

Uploading image from Android to my server (Permission Denied)

我正在尝试将图像 select 从图库上传到我的 Springboot 服务器,但是当我的服务尝试 post 图像时,文件路径的权限被拒绝。我已将这些权限添加到我的 AndroidManifest:

    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <!-- permission below just in case, should not be needed I believe -->
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

然后我实时请求 select 图像的权限,然后我想将它放在放大视图中,用户可以在其中提供有关图像的更多详细信息,然后将其添加到报告中我稍后会 post.

因为我遇到了这个权限问题,所以当我尝试提交包含图像 (Uri) 的 Report 对象时,我也再次请求权限。

但我仍然收到此错误:

Caused by: java.io.FileNotFoundException: /storage/emulated/0/DCIM/Camera/IMG_20200206_120434.jpg (Permission denied)

我在 google 上发现的关于此错误的每一次点击都会指向不请求此实时许可的人,但我相信我什至做过一次。

这是我的代码的一些相关片段:

else if (view.getId() == R.id.stubNewBreedingReportSelectImageButt) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE}, 1);
                } else {
                    getPhotoFromPhone(); // this starts the intent to pick an image
                }
            } 
        }

else if (view.getId() == R.id.stubNewBreedingReportSubmitButt) {
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                if (checkSelfPermission(Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
                    requestPermissions(new String[] {Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE}, 2);
                } else {
                    submitNewBreedingReport();
                }
            } 
        }

这是我的 onClick(View view) 方法。第一个有效,因为我被允许从画廊中挑选图像。第二个可能不需要根据我发现的从 android.

上传图像的项目的每个示例检查权限

在我的 onActivityResult(int requestCode, int resultCode, Intent data) 方法中我膨胀了这个 "add image details view"。我还将 selected Uri 作为私有 Uri selectedImg 存储在 activity 中以备将来使用。这一切似乎都很好。

然后当我提交图像时(在 submitNewReport() 方法中)我使用 ExecutorService (java class) 启动一个新的异步线程进行上传。在这个 Callable<> 中,我得到了一个 Springs RestTemplate 实例并尝试 post 图像,但是当我的 restTemplate 尝试调用并从我的 Uri 中获取文件时,我得到了拒绝的权限。

这是我的应用ImageService中的上传方法:

public Gallery uploadPictureWithInfo(Uri uri, Map<String,Object> imgParams, Context context) {
        if (uri.getPath() != null) {
            File resourceFile = new File(getPathFromUri(uri,context));
            //if (resourceFile.exists()) {
                Gallery saved = null;
                Map<String,Object> params = new HashMap<>();
                params.put(PARAM_FILE, new FileSystemResource(resourceFile));
                if (imgParams.get(PARAM_GALLERY_ID) != null || (long) imgParams.get(PARAM_GALLERY_ID) > (long) 0)  {
                    params.put(PARAM_GALLERY_ID, imgParams.get(PARAM_GALLERY_ID));
                    if (imgParams.get(PARAM_DESCRIPTION) != null) {
                        params.put(PARAM_DESCRIPTION, imgParams.get(PARAM_DESCRIPTION));
                    }
                    if (imgParams.get(PARAM_PHOTOGRAPH) != null) {
                        params.put(PARAM_PHOTOGRAPH, imgParams.get(PARAM_PHOTOGRAPH));
                    }
                    if (imgParams.get(PARAM_USER_ID) != null && (long) imgParams.get(PARAM_USER_ID) > 0) {
                        params.put(PARAM_USER_ID, imgParams.get(PARAM_USER_ID));
                    }
                    HttpEntity requestEntity = new HttpEntity<>(params, AquaDbConfig.getImageHeaders());
                    ResponseEntity<Gallery> responseEntity =
                            restTemplate.exchange(AquaDbConfig.getApiUrl() + "/images/uploadImgWithInfo", HttpMethod.POST, requestEntity, Gallery.class);
                    if (responseEntity.hasBody()) {
                        saved = responseEntity.getBody();
                    }
                    return saved;
                }
            //}
        }
        return null;
    }


public static String getPathFromUri(Uri uri, Context context) {
        String[] filePath = { MediaStore.Images.Media.DATA };
        Cursor c = context.getContentResolver().query(uri,filePath, null, null, null);
        c.moveToFirst();
        int columnIndex = c.getColumnIndex(filePath[0]);
        String picturePath = c.getString(columnIndex);
        c.close();
        return picturePath;
    }

我注释掉了 file.isExist() 的检查以通过该测试,否则它不会生成堆栈跟踪。

所以我的问题是,当我 POST 将图像文件发送到服务器时,我如何读取图像文件?我读了一些关于 FileProvider class 的内容,但在我看来,它是用来通过 Intents 将文件发送到新的 Activites 或其他应用程序的。在我看来,它似乎不是为此而设计的,因为我从不离开我的 Activity 除了在画廊中挑选图像。创建此 ReportedBreeding 对象的不同步骤由膨胀的 ViewStubs 而不是新活动处理。此外,我使用的 Uri 不是指我为我的应用程序创建的任何目录,而是指用户图片库(外部存储)。

我还尝试在 android 清单中将我的 ImageService 声明为服务,尽管我不确定我们谈论的是同一种服务。然后我添加了这个权限,但没有任何区别:

        <service
            android:name=".service.MyImageFactory"
            android:permission="android.permission.READ_EXTERNAL_STORAGE">
        </service>

如果您知道如何获得此 RestTemplate POST 方法的权限(在我审查的示例中似乎没有其他人需要)或者我如何解决这个问题,请分享!我开始有点沮丧和卡住了。我的问题是为什么 android 需要另一个权限检查以及如何在我的 uploadPictureWithInfo(..) 方法中提供或解决它?

尝试在 getPhotoFromPhone()

之前请求 WRITE_EXTERNAL_STORAGE 的许可

对于Android 10,这可能是权限问题,目前有两种解决方案可以解决。第一种方法是允许显示应用程序标签:android:requestLegacyExternalStorage="true"

另一种是使用openFileDescriptor

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)
val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)

fun ContentResolver.getFileName(fileUri: Uri): String {

    var name = ""
    val returnCursor = this.query(fileUri, null, null, null, null)
    if (returnCursor != null) {
        val nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
        returnCursor.moveToFirst()
        name = returnCursor.getString(nameIndex)
        returnCursor.close()
    }

    return name
}

val file = File(context.cacheDir, getFileName(context.contentResolver, fileUri))

val parcelFileDescriptor = context.contentResolver.openFileDescriptor(fileUri, "r", null)

parcelFileDescriptor?.let {
    val inputStream = FileInputStream(parcelFileDescriptor.fileDescriptor)
    val file = File(context.cacheDir, context.contentResolver.getFileName(fileUri))
    val outputStream = FileOutputStream(file)
    IOUtils.copy(inputStream, outputStream)
}