在 Android 上从照片 URI 创建文件

Posted

技术标签:

【中文标题】在 Android 上从照片 URI 创建文件【英文标题】:Create a file from a photo URI on Android 【发布时间】:2021-12-30 23:18:00 【问题描述】:

我有一个 android 应用程序需要让用户从图库中选择一些图片并将这些图片发送到后端(连同一些其他数据)。

为了允许用户选择图片,我的片段中有以下内容:

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

我在这里得到用户选择照片的结果:

@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() 回调中。

我发现了以下 *** 帖子 Reading File from Uri gives java.io.FileNotFoundException: open failed: ENOENT,但我不确定如何在针对我的特定情况的响应中实施一般建议。

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

普通软件建议

使用 ContentResolver 和 openInputStream() 在 Uri 指向的内容上获取 InputStream。然后,将其传递给您的解码逻辑,例如 BitmapFactory 及其 decodeStream() 方法。

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

任何帮助将不胜感激。

【问题讨论】:

【参考方案1】:

为了允许用户选择图片,我的片段中有以下内容:

此代码使用ACTION_GET_CONTENT。特别是在 Android 7.0+ 上,通常会返回带有 content 方案的 Uri 值。您的代码假定您使用file 方案获得Uri 值,其中路径实际上具有意义。此外,您的代码假定用户正在选择您可以访问的文件系统上的文件,并且没有任何东西迫使用户这样做。 ACTION_GET_CONTENT 可由其内容所在的应用支持:

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

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

This blog post 演示了如何使用InputStreamRequestBody(特别是原始的 Kotlin 端口)以这种方式上传内容。 This blog post 提供了对相同问题和类似解决方案的另一种看法。

【讨论】:

是的,你是对的。但请记住,我还需要在我的请求中发送一些其他键值对,而不仅仅是照片。我有这个:MultipartBody.Builder builder = new MultipartBody.Builder(); builder.setType(MultipartBody.FORM); builder.addFormDataPart("details", details); 在我的 Retrofit 界面中我有这个:@POST("ticket") Call&lt;GenerateNewTicketResponse&gt; generateNewTicket( @Header("Authorization") String accessToken, @Body MultipartBody payload); 我不确定这如何与新的 RequestBody 匹配。 @RobertRuxandrescu:“我不确定这如何与新的 RequestBody 匹配。” -- 在你问题的代码中,你有builder.addFormDataPart("photos[]", file.getName(), RequestBody.create(MediaType.parse("multipart/form-data"), file));。您可以将其替换为builder.addFormDataPart("photos[]", name, new InputStreamRequestBody(MediaType.parse("multipart/form-data"), cr, uris.get(i)));,其中crContentResolvername 您需要生成或可能从 DocumentFile.forSingleUri()getName() 获取。 @RobertRuxandrescu:除此之外,其余的改造内容将保持不变。

以上是关于在 Android 上从照片 URI 创建文件的主要内容,如果未能解决你的问题,请参考以下文章

ANDROID ACTION_VIEW 显示来自 URI 的错误图像

在 Android 中获取 RawContact 照片的 URI

Android 7.0下,拍摄照片报错

从 Android Lollipop 中的 Uri 裁剪照片后总是返回 Null?

使用 LOOKUP_URI 在 Android (API 8) 中获取联系人照片

Android 2.3 和 Android 4.0 上的联系人照片 uri