Retrofit+RxJava is already the most mainstream network framework on the market. It is extremely easy to use it to make ordinary network requests. I have used Retrofit to upload files and download files before, but I found that using Retrofit to download does not support progress callbacks by default, but the product requires that the download progress is displayed when downloading files, so I have to go into it.
Next, let's encapsulate it together and use Retrofit+RxJava to download files with progress.
github:JsDownload
Let’s take a look at the UML diagram first:
You may not be sure how to deal with it. Don’t worry, let’s take it step by step:
1. It is necessary to add dependence
compile 'io.reactivex:rxjava:1.1.0'compile 'io.reactivex:rxandroid:1.1.0'compile 'com.squareup.retrofit2:retrofit:2.0.0-beta4'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'
Pay attention to the version number when using it
2. Write callback
/** * Description: Download progress callback* Created by jia on 2017/11/30. * The reason why people can do it is because they believe they can*/public interface JsDownloadListener { void onStartDownload(); void onProgress(int progress); void onFinishDownload(); void onFail(String errorInfo);}Needless to say, the downloaded callback should at least have four callback methods: start downloading, download progress, download completion, and download failure.
Note that the progress percentage is returned in the onProgress method and the failure reason is returned in onFail.
3. Rewrite ResponseBody and calculate the download percentage
/** * Description: Download request body with progress* Created by jia on 2017/11/30. * The reason why people can do it is that they believe that they can*/public class JsResponseBody extends ResponseBody { private ResponseBody responseBody; private JsDownloadListener downloadListener; // BufferedSource is the input stream in the okio library, which is used here as an inputStream. private BufferedSource bufferedSource; public JsResponseBody(ResponseBody responseBody, JsDownloadListener downloadListener) { this.responseBody = responseBody; this.downloadListener = downloadListener; } @Override public MediaType contentType() { 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 source is 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())); } } return bytesRead; } } }; }}Pass the ResponseBody and JsDownloadListener for the network request in the construct.
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 is not enough to encapsulate ResponseBody only. The key is that we need to get the requested ResponseBody, here we use the Interceptor.
/** * Description: Download Interceptor with progress* Created by jia on 2017/11/30. * The reason why people can do it is that they believe they can*/public class JsDownloadInterceptor implements Interceptor { private JsDownloadListener downloadListener; public JsDownloadInterceptor(JsDownloadListener downloadListener) { this.downloadListener = downloadListener; } @Override public Response intercept(Chain chain) throws IOException { Response response = chain.proceed(chain.request()); return response.newBuilder().body( new JsResponseBody(response.body(), downloadListener)).build(); }}Normally, interceptors are used to add, remove, or convert request or response header information.
Returns the ResponseBody we just encapsulated in the intercept method intercept.
5. Network request service
/** * Description: * Created by jia on 2017/11/30. * The reason why people can do it is because they believe they can*/public interface DownloadService { @Streaming @GET Observable<ResponseBody> download(@Url String url);}Notice:
Here @Url is to pass in the complete download URL; you don't need to intercept the @Streaming annotation method
6. The final request start
/** 1. Description: Download tool class 2. Created by jia on 2017/11/30. 3. The reason why people can do it is that they believe they can*/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 of the code before it is called// observeOn() changes the thread of 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) { return responseBody.byteStream(); } }) .observeOn(Schedulers.computation()) // Used to calculate the task.doOnNext(new Action1<InputStream>() { @Override public void call(InputStream inputStream) { writeFile(inputStream, filePath); } }) .observeOn(AndroidSchedulers.mainThread()) .subscribe(subscriber); } /** * Write the input stream to the 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"); } }}Of course, you also need to pay attention to each location of the download callback.
The above is all the content of this article. I hope it will be helpful to everyone's learning and I hope everyone will support Wulin.com more.