Retrofit+RxJava is already the most mainstream web framework on the market, and it’s extremely easy to use it for ordinary web requests. I’ve done uploading and downloading files with Retrofit before, but I found that: Retrofit does not support progress callbacks by default, but the product requires that you show progress when downloading files, so you have to dig deeper.

Next we encapsulate together, using Retrofit+RxJava to implement the progress download file.

Github:github.com/shuaijia/Js…

Let’s start with a UML diagram:

You may not know exactly how to deal with it, don’t worry, let’s step by step:

Adding dependencies is a must

compile 'the IO. Reactivex: rxjava: 1.1.0'
compile 'the IO. Reactivex: rxandroid: 1.1.0'
compile 'com. Squareup. Retrofit2: retrofit: 2.0.0 - beta4'
compile 'com. Squareup. Retrofit2: converter - gson: 2.0.0 - beta4'
compile 'com. Squareup. Retrofit2: adapter - rxjava: 2.0.0 - beta4'
Copy the code

Note the version number when using

2. Write callback

/** * Description: Public interface JsDownloadListener {void onStartDownload(); public interface JsDownloadListener {void onStartDownload(); void onProgress(int progress); void onFinishDownload(); void onFail(String errorInfo); }Copy the code

There is no need to say more here, download callback, at least should have the start of download, download progress, download completed, download failed four callback methods.

Note that the onProgress method returns the progress percentage, and the onFail method returns the failure cause.

Rewrite the ResponseBody to calculate the percentage of downloads

/** * Description: * Created by JIA on 2017/11/30. * Created by JIA on 2017/11/30 */ public class JsResponseBody extends ResponseBody {private ResponseBody ResponseBody; private JsDownloadListener downloadListener; // BufferedSource is an inputStream in the okio library, and is used here as an inputStream. private BufferedSource bufferedSource; public JsResponseBody(ResponseBody responseBody, JsDownloadListener downloadListener) { this.responseBody = responseBody; this.downloadListener = downloadListener; } @Override public MediaTypecontentType() {
        return responseBody.contentType();
    }

    @Override
    public long contentLength() {
        return responseBody.contentLength();
    }

    @Override
    public BufferedSource source() {
        if (bufferedSource == null) {
            bufferedSource = Okio.buffer(source(responseBody.source()));
        }
        return bufferedSource;
    }

    private Source source(Source source) {
        return new ForwardingSource(source) {
            long totalBytesRead = 0L;

            @Override
            public long read(Buffer sink, long byteCount) throws IOException {
                long bytesRead = super.read(sink, byteCount);
                // read() returns the number of bytes read, or -1 if this sourceis exhausted. totalBytesRead += bytesRead ! = 1? bytesRead : 0; Log.e("download"."read: "+ (int) (totalBytesRead * 100 / responseBody.contentLength()));
                if(null ! = downloadListener) {if (bytesRead != -1) {
                        downloadListener.onProgress((int) (totalBytesRead * 100 / responseBody.contentLength()));
                    }

                }
                returnbytesRead; }}; }}Copy the code

Pass in the construct the ResponseBody and JsDownloadListener for the network request.

The core here is the source method, which returns the ForwardingSource object, where we override its read method, calculate the percentage in the read method, and pass it to the callback downloadListener.

4. Interceptor

It’s not enough to just encapsulate the ResponseBody. The key is to get the ResponseBody of the request, which we use in this case: Interceptor.

/** * Description: * Created by JIA on 2017/11/30. */ public class JsDownloadInterceptor implements Interceptor {private JsDownloadListener implements listener; public JsDownloadInterceptor(JsDownloadListener downloadListener) { this.downloadListener = downloadListener; } @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request());returnresponse.newBuilder().body( new JsResponseBody(response.body(), downloadListener)).build(); }}Copy the code

Typically interceptors are used to add, remove, or transform headers for requests or responses.

Return the ResponseBody we just encapsulated in the intercept method.

5, network request service

/** * Description: * Created by Jia on 2017/11/30. */ public Interface DownloadService {@streaming @get Observable<ResponseBody> download(@url String Url); }Copy the code

Note:

  • Here @url is the full download Url that was passed in; Don’t intercept
  • Use the @streaming annotation method

6. Finally start the request

/** 1. Description: download tool class 2. Created by JIA on 2017/11/30. */ public class DownloadUtils {private static final String TAG ="DownloadUtils";

    private static final int DEFAULT_TIMEOUT = 15;

    private Retrofit retrofit;

    private JsDownloadListener listener;

    private String baseUrl;

    private String downloadUrl;

    public DownloadUtils(String baseUrl, JsDownloadListener listener) {

        this.baseUrl = baseUrl;
        this.listener = listener;

        JsDownloadInterceptor mInterceptor = new JsDownloadInterceptor(listener);

        OkHttpClient httpClient = new OkHttpClient.Builder()
                .addInterceptor(mInterceptor)
                .retryOnConnectionFailure(true) .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) .build(); retrofit = new Retrofit.Builder() .baseUrl(baseUrl) .client(httpClient) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); } /** * Start download ** @param url * @param filePath * @param subscriber */ public void download(@nonnull String URL, final String filePath, Subscriber subscriber) { listener.onStartDownload(); // subscribeOn() changes the thread on which the code before it is called // observeOn() changes the thread on which the code after it is called retrofit.create(downloadservice.class).download(URL) .subscribeOn(Schedulers.io()) .unsubscribeOn(Schedulers.io()) .map(new Func1<ResponseBody, InputStream>() { @Override public InputStream call(ResponseBody responseBody) {returnresponseBody.byteStream(); }}).observeon (Schedulers.computation()).doonNext (new Action1<InputStream>() {@override public void call(InputStream inputStream) { writeFile(inputStream, filePath); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } /** * write InputStream to file ** @param inputString * @param filePath */ private void writeFile(InputStream inputString, String filePath) { File file = new File(filePath);if (file.exists()) {
            file.delete();
        }

        FileOutputStream fos = null;
        try {
            fos = new FileOutputStream(file);

            byte[] b = new byte[1024];

            int len;
            while((len = inputString.read(b)) ! = -1) { fos.write(b,0,len); } inputString.close(); fos.close(); } catch (FileNotFoundException e) { listener.onFail("FileNotFoundException");
        } catch (IOException e) {
            listener.onFail("IOException"); }}}Copy the code
  1. Pass in the download address and the last callback in the construct, or, of course, the save address;
  2. Add our custom interceptor to OkHttpClient;
  3. Attention. AddCallAdapterFactory (RxJavaCallAdapterFactory. The create ()) support RxJava;
  4. Use RxJava’s Map method to turn the responseBody into an input stream;
  5. Write the input stream to a file in doOnNext;

Of course, you need to be careful where you download the callbacks.

For more exciting content, please pay attention to the wechat official account — Android Vehicles!