Example of downloading files and implementing progress monitoring using Retrofit

  • 2021-10-11 19:30:27
  • OfStack

1. Preface

Recently, I want to do a function of downloading files with progress bar. After watching it on the Internet for 1 circle, I found that many of them are realized by adding interceptors based on OkHttpClient. Personally, I think it is slightly complicated, so I still use the simplest method to realize it: monitoring the progress based on file writing.

2. Implementation steps

2.1 Design the listening interface

Design the interface under 1 according to the requirements:


public interface DownloadListener {
  void onStart();// Download Start 

  void onProgress(int progress);// Download progress 

  void onFinish(String path);// Download complete 

  void onFail(String errorInfo);// Download failed 
}

If you still need download speed and so on, you can design your own interface and parameters.

2.2 Writing Network Interface Service


public interface DownloadService {

  @Streaming
  @GET
  Call<ResponseBody> download(@Url String url);
}

It is basically 1 with the normal interface writing. It should be noted that @ Streaming annotation should be added.

By default, Retrofit reads all of the server-side Response into memory before processing the results. If a very large file is returned on the server side, oom is prone to occur. The main function of @ Streaming is to write the bytes downloaded in real time to disk immediately, instead of reading the whole file into memory.

2.3 Start a network request


public class DownloadUtil {

  public static void download(String url, final String path, final DownloadListener downloadListener) {

    Retrofit retrofit = new Retrofit.Builder()
        .baseUrl("http://www.xxx.com")
        // Obtained through thread pool 1 Threads, specifying callback Run in child threads. 
        .callbackExecutor(Executors.newSingleThreadExecutor())
        .build();

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

    Call<ResponseBody> call = service.download(url);
    call.enqueue(new Callback<ResponseBody>() {
      @Override
      public void onResponse(@NonNull Call<ResponseBody> call, @NonNull final Response<ResponseBody> response) {
        // Will Response Write to slave disk, see the following analysis for details 
        // Note that this method runs in a child thread 
        writeResponseToDisk(path, response, downloadListener);
      }

      @Override
      public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable throwable) {
        downloadListener.onFail(" Network Error ~ ");
      }
    });
  }
}

In Retrofit, Callback runs in the main thread by default. If we write Response directly to disk, this operation runs directly in the main thread, it will report NetworkOnMainThreadException exception. Therefore, it must be put into a child thread to run.

So far, it is still a normal network request. So, it's still very simple. Here comes the highlight.

2.4 Monitor Download Progress


private static void writeResponseToDisk(String path, Response<ResponseBody> response, DownloadListener downloadListener) {
    // From response Get the input stream and the total size 
    writeFileFromIS(new File(path), response.body().byteStream(), response.body().contentLength(), downloadListener);
  }

  private static int sBufferSize = 8192;

  // Write the input stream to a file 
  private static void writeFileFromIS(File file, InputStream is, long totalLength, DownloadListener downloadListener) {
    // Start downloading 
    downloadListener.onStart();

    // Create a file 
    if (!file.exists()) {
      if (!file.getParentFile().exists())
        file.getParentFile().mkdir();
      try {
        file.createNewFile();
      } catch (IOException e) {
        e.printStackTrace();
        downloadListener.onFail("createNewFile IOException");
      }
    }

    OutputStream os = null;
    long currentLength = 0;
    try {
      os = new BufferedOutputStream(new FileOutputStream(file));
      byte data[] = new byte[sBufferSize];
      int len;
      while ((len = is.read(data, 0, sBufferSize)) != -1) {
        os.write(data, 0, len);
        currentLength += len;
        // Calculate the current download progress 
        downloadListener.onProgress((int) (100 * currentLength / totalLength));
      }
      // Download is complete and return to the saved file path 
      downloadListener.onFinish(file.getAbsolutePath());
    } catch (IOException e) {
      e.printStackTrace();
      downloadListener.onFail("IOException");
    } finally {
      try {
        is.close();
      } catch (IOException e) {
        e.printStackTrace();
      }
      try {
        if (os != null) {
          os.close();
        }
      } catch (IOException e) {
        e.printStackTrace();
      }
    }
  }

Therefore, in fact, it is to monitor the progress by listening to the writing of files.

2.5 Use Examples


String url = "";
      String path = "";
      DownloadUtil.download(url, path, new DownloadListener() {
        @Override
        public void onStart() {
          // Run on child threads 
        }

        @Override
        public void onProgress(int progress) {
          // Run on child threads 
        }

        @Override
        public void onFinish(String path) {
          // Run on child threads 
        }

        @Override
        public void onFail(String errorInfo) {
          // Run on child threads 
        }
      });

Note that the above callbacks are all run in child threads. If you need to update UI and other operations, you can use Handler and other updates.


Related articles: