Retrofit 2 - How to download files from the server

Keywords: Retrofit Android network Java

This article is reproduced from: http://www.jianshu.com/p/92bb85fc07e8.

How to declare a Retrofit request

If you haven't written any lines of Retrofit request code before reading this article, it's better to take a look at the previous blogs. For many Retrofit users, a request to define a download file is almost identical to other requests:

// option 1: a resource relative to your base URL
@GET("/resource/example.zip")
Call<ResponseBody> downloadFileWithFixedUrl();

// option 2: using a dynamic URL
@GET
Call<ResponseBody> downloadFileWithDynamicUrlSync(@Url String fileUrl);

If the file you want to download is a static resource (the same location on the server), the Base URL points to the server where it is located. In this case, you can choose to use Plan 1. As you can see, it looks like a normal Ratofit 2 request. It is worth noting that we have ResponseBody as the return type. Retrofit tries to parse and convert it, so you can't use any other return type, otherwise when you download a file, it's meaningless.

The second scenario is a new feature of Retrofit 2. Now you can easily construct a dynamic address as a full path request. This is very useful for downloading some special files, that is to say, the request may depend on some parameters, such as user information or timestamp. You can construct URL addresses at runtime and request files accurately. If you haven't tried dynamic URL yet, you can turn to the beginning and read this blog. Retrofit Dynamic URL in 2.

Which one is useful for you? Let's go on.

How to invoke a request

After declaring the request, the actual invocation is as follows:

FileDownloadService downloadService = ServiceGenerator.create(FileDownloadService.class);

Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);

call.enqueue(new Callback<ResponseBody>() {  
    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccess()) {
            Log.d(TAG, "server contacted and has file");

            boolean writtenToDisk = writeResponseBodyToDisk(response.body());

            Log.d(TAG, "file download was a success? " + writtenToDisk);
        } else {
            Log.d(TAG, "server contact failed");
        }
    }

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        Log.e(TAG, "error");
    }
});

If you are confused about Service Generator. create (), you can read our First Blog . Once the service is created, we can make network requests just like other Retrofit calls.

One important thing left is the writeResponseBodyToDisk() function hidden in the code block: responsible for writing files to disk.

How to save files

The writeResponseBodyToDisk() method holds the ResponseBody object by reading its bytes and writing them to disk. The code looks a little more complicated than it actually is:

private boolean writeResponseBodyToDisk(ResponseBody body) {  
    try {
        // todo change the file location/name according to your needs
        File futureStudioIconFile = new File(getExternalFilesDir(null) + File.separator + "Future Studio Icon.png");

        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            byte[] fileReader = new byte[4096];

            long fileSize = body.contentLength();
            long fileSizeDownloaded = 0;

            inputStream = body.byteStream();
            outputStream = new FileOutputStream(futureStudioIconFile);

            while (true) {
                int read = inputStream.read(fileReader);

                if (read == -1) {
                    break;
                }

                outputStream.write(fileReader, 0, read);

                fileSizeDownloaded += read;

                Log.d(TAG, "file download: " + fileSizeDownloaded + " of " + fileSize);
            }

            outputStream.flush();

            return true;
        } catch (IOException e) {
            return false;
        } finally {
            if (inputStream != null) {
                inputStream.close();
            }

            if (outputStream != null) {
                outputStream.close();
            }
        }
    } catch (IOException e) {
        return false;
    }
}

Most of them are boilerplate code for general Java I/O streams. You just need to care about the first line of code, which is what name the file is ultimately saved. When you're done, you can use Retrofit to download files.

But we are not fully prepared. And there's a big problem here: By default, Retrofit reads the entire Server Response into memory before processing the results, which works well on JSON or XML and other Responses, but if it's a very large file, it can cause an OutofMemory exception.

If your application needs to download a slightly larger file, we strongly recommend reading the next section.

Beware of big files: Please use @Streaming!

If you download a very large file, Retrofit will try to read the entire file into memory. To avoid this, we added a special comment to declare the request.

@Streaming
@GET
Call<ResponseBody> downloadFileWithDynamicUrlAsync(@Url String fileUrl);

Declaring @Streaming does not mean that you need to observe a Netflix file. It means passing bytecode immediately without having to read the entire file into memory. It's worth noting that if you use @Streaming and still use the above code snippets for processing. Android will throw an android.os.NetworkOnMainThreadException exception.

So the last step is to put these operations into a single worker thread, such as ASyncTask:

final FileDownloadService downloadService =  
                ServiceGenerator.create(FileDownloadService.class);

new AsyncTask<Void, Long, Void>() {  
   @Override
   protected Void doInBackground(Void... voids) {
       Call<ResponseBody> call = downloadService.downloadFileWithDynamicUrlSync(fileUrl);
       call.enqueue(new Callback<ResponseBody>() {
           @Override
           public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
               if (response.isSuccess()) {
                   Log.d(TAG, "server contacted and has file");

                   boolean writtenToDisk = writeResponseBodyToDisk(response.body());

                   Log.d(TAG, "file download was a success? " + writtenToDisk);
               }
               else {
                   Log.d(TAG, "server contact failed");
               }
           }
       return null;
   }
}.execute();

So far, if you can remember the use of @Streaming and the above code fragments, you can use Retrofit to download large files efficiently.



Author: Xiao Deng Zi
Link: http://www.jianshu.com/p/92bb85fc07e8
Source: Brief Book
Copyright belongs to the author. For commercial reprints, please contact the author for authorization. For non-commercial reprints, please indicate the source.

Posted by dacio on Mon, 27 May 2019 13:23:46 -0700