Retrofit 2 文件下载/上传

32

我正在尝试使用Retrofit 2下载/上传文件,但无法找到任何关于如何执行此操作的教程或示例。

我的下载代码如下:

@GET("documents/checkout")
public Call<File> checkout(@Query(value = "documentUrl") String documentUrl, @Query(value = "accessToken") String accessToken, @Query(value = "readOnly") boolean readOnly);

并且

Call<File> call = RetrofitSingleton.getInstance(serverAddress)
                .checkout(document.getContentUrl(), apiToken, readOnly[i]);
call.enqueue(new Callback<File>() {
        @Override
        public void onResponse(Response<File> response,
                Retrofit retrofit) {
            String fileName = document.getFileName();
            try {
                System.out.println(response.body());
                long fileLength = response.body().length();
                InputStream input = new FileInputStream(response.body());
                File path = Environment.getExternalStorageDirectory();
                File file = new File(path, fileName);
                BufferedOutputStream output = new BufferedOutputStream(
                        new FileOutputStream(file));
                byte data[] = new byte[1024];

                long total = 0;
                int count;
                while ((count = input.read(data)) != -1) {
                    total += count;
                    output.write(data, 0, count);
                }

                output.flush();

                output.close();
            } catch (IOException e) {
                String logTag = "TEMPTAG";
                Log.e(logTag, "Error while writing file!");
                Log.e(logTag, e.toString());
            }
        }
        @Override
        public void onFailure(Throwable t) {
            // TODO: Error handling
            System.out.println(t.toString());
        }
    });

我已经尝试了 Call 和 Call,但似乎都不起作用。
服务器端代码在正确设置头和MIME类型后,将文件的字节写入 HttpServletResponse 的输出流中。
我做错了什么?
最后,上传的代码:
@Multipart
@POST("documents/checkin")
public Call<String> checkin(@Query(value = "documentId") String documentId, @Query(value = "name") String fileName, @Query(value = "accessToken") String accessToken, @Part("file") RequestBody file);

并且

RequestBody requestBody = RequestBody.create(MediaType.parse(document.getMimeType()), file);

            Call<String> call = RetrofitSingleton.getInstance(serverAddress).checkin(documentId, document.getFileName(), apiToken, requestBody);
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Response<String> response, Retrofit retrofit) {
                    System.out.println(response.body());
                }

                @Override
                public void onFailure(Throwable t) {
                    System.out.println(t.toString());
                }
            });

谢谢!

编辑:

在得到答案后,仅下载会产生损坏的文件(没有 @Streaming),上传也不行。当我使用上述代码时,服务器会返回400错误。将其更改为:

RequestBody requestBody = RequestBody.create(MediaType.parse(document.getMimeType()), file);
            MultipartBuilder multipartBuilder = new MultipartBuilder();
            multipartBuilder.addFormDataPart("file", document.getFileName(), requestBody);

            Call<String> call = RetrofitSingleton.getInstance(serverAddress).checkin(documentId, document.getFileName(), apiToken, multipartBuilder.build());

请求已执行,但后端似乎没有接收到文件。

后端代码:

@RequestMapping(value = "/documents/checkin", method = RequestMethod.POST)
public void checkInDocument(@RequestParam String documentId,
        @RequestParam String name, @RequestParam MultipartFile file,
        @RequestParam String accessToken, HttpServletResponse response)

我做错了什么?我能够使用 Apache HttpClient 平稳地从普通 Java 后端进行操作:

    MultipartEntityBuilder builder = MultipartEntityBuilder.create();
    builder.setMode(HttpMultipartMode.BROWSER_COMPATIBLE);
    builder.addBinaryBody("file", new File("E:\\temp\\test.jpg"));
    HttpEntity httpEntity = builder.build();
    System.out.println("HttpEntity " + EntityUtils.toString(httpEntity.));
    HttpPost httpPost = new HttpPost(uri);
    httpPost.setEntity(httpEntity);

编辑v2

对于任何感兴趣的人,上传和下载现在都可以正常工作:以下是解决方案:

服务:

@GET("documents/checkout")
public Call<ResponseBody> checkout(@Query(value = "documentUrl") String documentUrl, @Query(value = "accessToken") String accessToken, @Query(value = "readOnly") boolean readOnly);

@Multipart
@POST("documents/checkin")
public Call<String> checkin(@Query(value = "documentId") String documentId, @Query(value = "name") String fileName, @Query(value = "accessToken") String accessToken, @Part("file") RequestBody file);

下载代码:

    Call<ResponseBody> call = RetrofitSingleton.getInstance(serverAddress)
                .checkout(document.getContentUrl(), apiToken, readOnly[i]);
        call.enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Response<ResponseBody> response,
                    Retrofit retrofit) {
                String fileName = document.getFileName();

                try {
                    File path = Environment.getExternalStorageDirectory();
                    File file = new File(path, fileName);
                    FileOutputStream fileOutputStream = new FileOutputStream(file);
                    IOUtils.write(response.body().bytes(), fileOutputStream);
                } catch (IOException e) {
                    Log.e(logTag, "Error while writing file!");
                    Log.e(logTag, e.toString());
                }
            }

            @Override
            public void onFailure(Throwable t) {
                // TODO: Error handling
                System.out.println(t.toString());
            }
        });

上传代码:

    Call<String> call = RetrofitSingleton
                    .getInstance(serverAddress).checkin(documentId,
                            document.getFileName(), apiToken,
                            multipartBuilder.build());
            call.enqueue(new Callback<String>() {
                @Override
                public void onResponse(Response<String> response,
                        Retrofit retrofit) {
                    // Handle response here
                }

                @Override
                public void onFailure(Throwable t) {
                    // TODO: Error handling
                    System.out.println("Error");
                    System.out.println(t.toString());
                }
            });

你收到了哪些日志信息?你可以设置日志级别 restAdapter.setLogLevel(LogLevel.FULL); - Eoin
通过Retrofit.client().interceptors().add添加日志记录后,问题似乎在于content-length始终为0,但我不知道原因,文件在文件系统中存在。 - N4zroth
请问您能否提供Spring服务器的完整下载方法更新吗?谢谢。 - Alberto Crespo
我很高兴我们有这段代码/功能,但是我们是否应该使用Retrofit上传和下载文件呢?就上传而言,我观察到的是,Retrofit在将整个文件发送到服务器之前会将其全部读入内存中。如果用户选择了一个大文件,那么在Android上很容易达到应用程序允许的最大堆大小限制。有人遇到过这个问题吗?有其他方法进行上传吗? - Nitesh Mudireddy
5个回答

28

为了下载,你可以使用ResponseBody作为返回类型 --

@GET("documents/checkout")
@Streaming
public Call<ResponseBody> checkout(@Query("documentUrl") String documentUrl, @Query("accessToken") String accessToken, @Query("readOnly") boolean readOnly);

您可以在回调函数中获取ResponseBody的输入流--

Call<ResponseBody> call = RetrofitSingleton.getInstance(serverAddress)
            .checkout(document.getContentUrl(), apiToken, readOnly[i]);

call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Response<ResponseBody> response,
                Retrofit retrofit) {
            String fileName = document.getFileName();
            try {
                InputStream input = response.body().byteStream();
                //  rest of your code

如果您的服务器正确处理多部分消息,那么乍一看,您上传的内容似乎还可以。它是否正常工作?如果没有,您能解释一下故障模式吗?您也可以尝试简化,方法是不要制作多部分内容。删除@Multipart注释并将@Path转换为@Body——

@POST("documents/checkin")
public Call<String> checkin(@Query("documentId") String documentId, @Query("name") String fileName, @Query("accessToken") String accessToken, @Body RequestBody file);

谢谢,下载现在可以了,只有上传不行 - 请参见编辑部分的问题描述 :-) - N4zroth
@iagreen,你能看一下这个问题吗?我需要帮助。https://stackoverflow.com/questions/53343332/send-image-file-via-retrofit-from-android-to-spring/53343958?noredirect=1#comment93567370_53343958 - Avi Patel

5
我正在使用retrofit 2.0.0-beta2,并且在使用multipart请求上传图像时遇到了问题。我通过使用以下答案解决了这个问题:https://dev59.com/EFwY5IYBdhLWcg3waXEE#32796626

对我来说关键是使用带有MultipartRequestBody的标准POST,而不是@Multipart注释请求。

这是我的代码:

Retrofit服务类

@POST("photo")
Call<JsonElement> uploadPhoto(@Body RequestBody imageFile, @Query("sessionId"));

在活动,片段中使用:

RequestBody fileBody = RequestBody.create(MediaType.parse("image/jpeg"), imageFile);
MultipartBuilder multipartBuilder = new MultipartBuilder();
multipartBuilder.addFormDataPart("photo", imageFile.getName(), fileBody);
RequestBody fileRequestBody = multipartBuilder.build();

//call
mRestClient.getRetrofitService().uploadProfilePhoto(fileRequestBody, sessionId);

6
在okhttp3中,MultipartBuilder现在改名为MultipartBody.Builder。 - Greg T

1

1

您可以参考 使用Retrofit 2.0下载图片的教程

目前,您可以参考以下函数进行图像下载:

void getRetrofitImage() {

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(url)
            .addConverterFactory(GsonConverterFactory.create())
            .build();

    RetrofitImageAPI service = retrofit.create(RetrofitImageAPI.class);

    Call<ResponseBody> call = service.getImageDetails();

    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Response<ResponseBody> response, Retrofit retrofit) {

            try {

                Log.d("onResponse", "Response came from server");

                boolean FileDownloaded = DownloadImage(response.body());

                Log.d("onResponse", "Image is downloaded and saved ? " + FileDownloaded);

            } catch (Exception e) {
                Log.d("onResponse", "There is an error");
                e.printStackTrace();
            }

        }

        @Override
        public void onFailure(Throwable t) {
            Log.d("onFailure", t.toString());
        }
    });
}

以下是使用Retrofit 2.0进行文件处理的图像下载部分。
private boolean DownloadImage(ResponseBody body) {

    try {
        Log.d("DownloadImage", "Reading and writing file");
        InputStream in = null;
        FileOutputStream out = null;

        try {
            in = body.byteStream();
            out = new FileOutputStream(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
            int c;

            while ((c = in.read()) != -1) {
                out.write(c);
            }
        }
        catch (IOException e) {
            Log.d("DownloadImage",e.toString());
            return false;
        }
        finally {
            if (in != null) {
                in.close();
            }
            if (out != null) {
                out.close();
            }
        }

        int width, height;
        ImageView image = (ImageView) findViewById(R.id.imageViewId);
        Bitmap bMap = BitmapFactory.decodeFile(getExternalFilesDir(null) + File.separator + "AndroidTutorialPoint.jpg");
        width = 2*bMap.getWidth();
        height = 6*bMap.getHeight();
        Bitmap bMap2 = Bitmap.createScaledBitmap(bMap, width, height, false);
        image.setImageBitmap(bMap2);

        return true;

    } catch (IOException e) {
        Log.d("DownloadImage",e.toString());
        return false;
    }
}

我希望这会有所帮助。祝一切顺利。编程愉快 :)


1

我也遇到了这个问题,以下是我尝试解决问题的方法(RETROFIT 2)。

 //1. What We Need From Server ( upload.php Script )
    public class FromServer {
        String result;
    }

    //2. Which Interface To Communicate Our upload.php Script?
    public interface ServerAPI {

        @Multipart
        @POST("upload.php")//Our Destination PHP Script
        Call<List<FromServer>> upload(
                @Part("file_name") String file_name,
                @Part("file") RequestBody description);

         Retrofit retrofit =
                new Retrofit.Builder()
                        .baseUrl("http://192.168.43.135/retro/") // REMEMBER TO END with /
                        .addConverterFactory(GsonConverterFactory.create())
                 .build();
    }


    //3. How To Upload
    private void upload(){

            ServerAPI api = ServerAPI.retrofit.create(ServerAPI.class);

            File from_phone = FileUtils.getFile(Environment.getExternalStorageDirectory()+"/aa.jpg"); //org.apache.commons.io.FileUtils
            RequestBody to_server = RequestBody.create(MediaType.parse("multipart/form-data"), from_phone);

            api.upload(from_phone.getName(),to_server).enqueue(new Callback<List<FromServer>>() {
                @Override
                public void onResponse(Call<List<FromServer>> call, Response<List<FromServer>> response) {
                    Toast.makeText(MainActivity.this, response.body().get(0).result, Toast.LENGTH_SHORT).show();
                }
                @Override
                public void onFailure(Call<List<FromServer>> call, Throwable t) { }
            });


         }

//4. upload.php
<?php

    $pic = $_POST['file_name'];

    $pic = str_replace("\"", "", $pic); //REMOVE " from file name
    if(file_exists($pic)){unlink($pic);}

    $f = fopen($pic, "w");
    fwrite($f,$_POST['file']);
    fclose($f);

    $arr[] = array("result"=>"Done");
    print(json_encode($arr));
?>

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接