简体   繁体   English

在 Android 上从照片 URI 创建文件

[英]Create a file from a photo URI on Android

I have an Android app that needs to let the user select some pictures from the gallery and send these pictures to the backend (together with some other data).我有一个 Android 应用程序需要让用户从图库中选择一些图片并将这些图片发送到后端(连同其他一些数据)。

To allow the user to select the pictures I have the following in my Fragment:为了允许用户选择图片,我的片段中有以下内容:

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

I get the result of the selected photos by the user in here:我在这里得到用户选择照片的结果:

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

I then send the list of URIs to the Presenter, where I execute my MultiPart Retrofit call to the backend:然后,我将 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);

The problem is that this sometimes works, sometimes doesn't.问题是这有时有效,有时无效。 Sometimes I get the error "java.io.FileNotFoundException", which throws me in the onFailure() callback of the Retrofit call.有时我会收到错误“java.io.FileNotFoundException”,这让我进入了 Retrofit 调用的onFailure()回调。

I found the following stackoverflow post Reading File from Uri gives java.io.FileNotFoundException: open failed: ENOENT but I'm not exactly sure how to implement the general suggestion in that response to my particular situation.我发现以下stackoverflow帖子从Uri读取文件给出了java.io.FileNotFoundException:打开失败:ENOENT ,但我不确定如何在针对我的特定情况的响应中实施一般建议。

What would be the right way to get the right path towards the pictures selected by the user such that I can create files out of them and attach them in my MultiPart request?什么是正确的方法来获得用户选择的图片的正确路径,以便我可以从中创建文件并将它们附加到我的 MultiPart 请求中?

Commonsware suggested to Commonsware 建议

Use a ContentResolver and openInputStream() to get an InputStream on the content pointed to by the Uri.使用 ContentResolver 和 openInputStream() 在 Uri 指向的内容上获取 InputStream。 Then, pass that to your decoding logic, such as BitmapFactory and its decodeStream() method.然后,将其传递给您的解码逻辑,例如 BitmapFactory 及其 decodeStream() 方法。

, but I'm not sure exactly how to do that programmatically. ,但我不确定如何以编程方式执行此操作。

Any help would be appreciated.任何帮助,将不胜感激。

To allow the user to select the pictures I have the following in my Fragment:为了允许用户选择图片,我的片段中有以下内容:

This code is using ACTION_GET_CONTENT .此代码使用ACTION_GET_CONTENT Particularly on Android 7.0+, generally that (and ACTION_OPEN_DOCUMENT ) will return Uri values with a content scheme.特别是在 Android 7.0+ 上,通常(和ACTION_OPEN_DOCUMENT )将返回带有content方案的Uri值。 Your code assumes that you are getting Uri values with a file scheme, where the path actually has meaning.您的代码假定您使用file方案获取Uri值,其中路径实际上具有意义。 Moreover, your code assumes that the user is picking files on the filesystem that you can access, and there is nothing that forces the user to do that.此外,您的代码假定用户正在选择您可以访问的文件系统上的文件,并且没有任何东西迫使用户这样做。 ACTION_GET_CONTENT can be supported by apps where their content is: ACTION_GET_CONTENT可由其内容为以下内容的应用程序支持:

  • A local file on external storage外部存储上的本地文件
  • A local file on internal storage for the other app另一个应用程序的内部存储上的本地文件
  • A local file on removable storage可移动存储上的本地文件
  • A local file that is encrypted and needs to be decrypted on the fly已加密且需要即时解密的本地文件
  • A stream of bytes held in a BLOB column in a database保存在数据库的BLOB列中的字节流
  • A piece of content on the Internet that needs to be downloaded by the other app first互联网上的一段内容,需要先被其他app下载
  • Content that is generated on the fly即时生成的内容
  • ...and so on ...等等

Instead of using RequestBody.create() , use the InputStreamRequestBody from this OkHttp issue comment .而不是使用RequestBody.create() ,使用来自这个 OkHttp 问题评论InputStreamRequestBody You provide the same media type as before, but instead of a File (that you are incorrectly creating), you provide a ContentResolver (from getContentResolver() on a Context ) and the Uri .您提供与以前相同的媒体类型,但不是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. 这篇博文演示了如何使用InputStreamRequestBody (特别是原始的 Kotlin 端口)以这种方式上传内容。 This blog post provides another look at the same problem and a similar solution.这篇博客文章提供了对相同问题和类似解决方案的另一种看法。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM