从 Android 上的照片 URI 创建文件

Create a file from a photo URI on Android

我有一个 Android 应用程序,它需要让用户 select 画廊中的一些图片并将这些图片发送到后端(连同一些其他数据)。

为了让用户select 我的片段中有以下图片:

private void pickImages() {
    Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
    intent.setType("image/*");
    intent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true);
    startActivityForResult(intent, PICK_PHOTO_FOR_AVATAR);
}

我在这里得到了用户 selected 照片的结果:

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if (requestCode == PICK_PHOTO_FOR_AVATAR && resultCode == Activity.RESULT_OK) {
        if (data == null) {
            //Display an error
            Toast.makeText(getActivity(), "There was an error getting the pictures", Toast.LENGTH_LONG).show();
            return;
        }

        ClipData clipData = data.getClipData();
        String fileName = null, extension = null;

        //if ClipData is null, then we have a regular file
        if (clipData == null) {
            //get the selected file uri
            fileName = FileUtils.getPath(getActivity(), data.getData());
            //obtain the extension of the file
            int index = fileName.lastIndexOf('.');
            if (index > 0) {
                extension = fileName.substring(index + 1);
                if (extension.equals("jpg") || extension.equals("png") || extension.equals("bmp") || extension.equals("jpeg"))
                    isAttachedFile = true;
            }
        }

        ArrayList<Uri> photosUris = new ArrayList<>();

        //for each image in the list of images, add it to the filesUris
        if (clipData != null) for (int i = 0; i < clipData.getItemCount(); i++) {
            ClipData.Item item = clipData.getItemAt(i);
            Uri uri = item.getUri();
            switch (i) {
                case 0:
                    picture1Uri = uri;
                    break;
                case 1:
                    picture2Uri = uri;
                    break;
            }
            photosUris.add(uri);
        }
        else if (isAttachedFile) {
            Uri uri = Uri.parse(fileName);
            picture1Uri = uri;
            photosUris.add(uri);
        }

        uris = photosUris;

        if (picture1Uri != null) {
            image1.setVisibility(View.VISIBLE);
            image1.setImageURI(picture1Uri);
        }
        if (picture2Uri != null) {
            image2.setVisibility(View.VISIBLE);
            image2.setImageURI(picture2Uri);
        }
    }

然后我将 URI 列表发送到 Presenter,在那里我对后端执行 MultiPart Retrofit 调用:

//obtain the file(s) information of the message, if any
    if (uris != null && uris.size() > 0) {
        for (int i = 0; i < uris.size(); i++) {
            File file = null;

            //this is the corect way to encode the pictures
            String encodedPath = uris.get(i).getEncodedPath();
            file = new File(encodedPath);

            builder.addFormDataPart("photos[]", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));
        }
    }

    MultipartBody requestBody = builder.build();

    //send the newly generated ticket
    Call<GenerateNewTicketResponse> generateNewTicketCall = OperatorApplication.getApiClient().generateNewTicket(Constants.BEARER + accessToken, requestBody);

问题是这有时有效,有时无效。有时我收到错误 "java.io.FileNotFoundException",这让我进入了 Retrofit 调用的 onFailure() 回调。

我发现了以下 Whosebug post 但我不确定如何在针对我的特定情况的响应中实施一般建议。

获得用户编辑的图片的正确路径的正确方法是什么 select 以便我可以从中创建文件并将它们附加到我的 MultiPart 请求中?

Commonsware 建议

Use a ContentResolver and openInputStream() to get an InputStream on the content pointed to by the Uri. Then, pass that to your decoding logic, such as BitmapFactory and its decodeStream() method.

,但我不确定具体如何以编程方式执行此操作。

如有任何帮助,我们将不胜感激。

To allow the user to select the pictures I have the following in my Fragment:

此代码正在使用 ACTION_GET_CONTENT。特别是在 Android 7.0+ 上,通常(和 ACTION_OPEN_DOCUMENT)将 return Uri 值与 content 方案。您的代码假定您使用 file 方案获得 Uri 值,其中路径实际上有意义。此外,您的代码假定用户正在选择您可以访问的文件系统上的文件,并且没有任何强制用户这样做的东西。 ACTION_GET_CONTENT 可以被以下内容的应用支持:

  • 外部存储上的本地文件
  • 另一个应用程序的内部存储上的本地文件
  • 可移动存储上的本地文件
  • 本地加密文件,需要即时解密
  • 数据库中 BLOB 列中保存的字节流
  • 网上一段内容,需要其他app先下载
  • 即时生成的内容
  • ...等等

不使用 RequestBody.create(),而是使用 this OkHttp issue comment 中的 InputStreamRequestBody。您提供与以前相同的媒体类型,但您提供的不是 File(您创建的不正确),而是 ContentResolver(来自 Context 上的 getContentResolver())和Uri.

This blog post demonstrates how to use InputStreamRequestBody (specifically a Kotlin port of the original) to upload content in this fashion. This blog post 提供了对同一问题和类似解决方案的另一种看法。