下载文件是一个比拟常见的需要。给定一个url,咱们能够应用URLConnection下载文件。 应用OkHttp也能够通过流来下载文件。 给OkHttp中增加拦截器,即可实现下载进度的监听性能。

应用流来实现下载文件

获取并应用字节流,须要留神两个要点,一个是服务接口办法的 @Streaming 注解,另一个是获取到 ResponseBody。

获取流(Stream)。先定义一个服务ApiService。给办法增加上@Streaming的注解。

private interface ApiService {        @Streaming        @GET        Observable<ResponseBody> download(@Url String url);    }

初始化OkHttp。记得填入你的baseUrl。

OkHttpClient okHttpClient = new OkHttpClient.Builder()            .connectTimeout(8, TimeUnit.SECONDS)            .build();    retrofit = new Retrofit.Builder()            .client(okHttpClient)            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())            .baseUrl("https://yourbaseurl.com")            .build();

发动网络申请。获取到 ResponseBody。

String downUrl = "xxx.com/aaa.apk";    retrofit.create(ApiService.class)            .download(downUrl)            .subscribeOn(Schedulers.io())            .observeOn(Schedulers.io())            .doOnNext(new Consumer<ResponseBody>() {                @Override                public void accept(ResponseBody responseBody) throws Exception {                    // 解决 ResponseBody 中的流                }            })            .doOnError(new Consumer<Throwable>() {                @Override                public void accept(Throwable throwable) throws Exception {                    Log.e(TAG, "accept on error: " + downUrl, throwable);                }            })            .observeOn(AndroidSchedulers.mainThread())            .subscribe(new Observer<ResponseBody>() {                @Override                public void onSubscribe(Disposable d) {                }                @Override                public void onNext(ResponseBody responseBody) {                }                @Override                public void onError(Throwable e) {                    Log.e(TAG, "Download center retrofit onError: ", e);                }                @Override                public void onComplete() {                }            });

通过 ResponseBody 拿到字节流 body.byteStream()。这里会先创立一个临时文件tmpFile,把数据写到临时文件里。 下载实现后再重命名成指标文件targetFile。

public void saveFile(ResponseBody body) {        state = DownloadTaskState.DOWNLOADING;        byte[] buf = new byte[2048];        int len;        FileOutputStream fos = null;        try {            Log.d(TAG, "saveFile: body content length: " + body.contentLength());            srcInputStream = body.byteStream();            File dir = tmpFile.getParentFile();            if (dir == null) {                throw new FileNotFoundException("target file has no dir.");            }            if (!dir.exists()) {                boolean m = dir.mkdirs();                onInfo("Create dir " + m + ", " + dir);            }            File file = tmpFile;            if (!file.exists()) {                boolean c = file.createNewFile();                onInfo("Create new file " + c);            }            fos = new FileOutputStream(file);            long time = System.currentTimeMillis();            while ((len = srcInputStream.read(buf)) != -1 && !isCancel) {                fos.write(buf, 0, len);                int duration = (int) (System.currentTimeMillis() - time);                int overBytes = len - downloadBytePerMs() * duration;                if (overBytes > 0) {                    try {                        Thread.sleep(overBytes / downloadBytePerMs());                    } catch (Exception e) {                        e.printStackTrace();                    }                }                time = System.currentTimeMillis();                if (isCancel) {                    state = DownloadTaskState.CLOSING;                    srcInputStream.close();                    break;                }            }            if (!isCancel) {                fos.flush();                boolean rename = tmpFile.renameTo(targetFile);                if (rename) {                    setState(DownloadTaskState.DONE);                    onSuccess(url);                } else {                    setState(DownloadTaskState.ERROR);                    onError(url, new Exception("Rename file fail. " + tmpFile));                }            }        } catch (FileNotFoundException e) {            Log.e(TAG, "saveFile: FileNotFoundException ", e);            setState(DownloadTaskState.ERROR);            onError(url, e);        } catch (Exception e) {            Log.e(TAG, "saveFile: IOException ", e);            setState(DownloadTaskState.ERROR);            onError(url, e);        } finally {            try {                if (srcInputStream != null) {                    srcInputStream.close();                }                if (fos != null) {                    fos.close();                }            } catch (IOException e) {                Log.e(TAG, "saveFile", e);            }            if (isCancel) {                onCancel(url);            }        }    }

每次读数据的循环,计算读了多少数据和用了多少工夫。超过限速后被动sleep一下,达到管制下载速度的成果。 要留神不能sleep太久,免得socket敞开。 这里管制的是网络数据流与本地文件的读写速度。

下载进度监听

OkHttp实现下载进度监听,能够从字节流的读写那里动手。也能够应用拦截器,参考官网的例子。 这里用拦截器的形式实现网络下载进度监听性能。

定义回调与网络拦截器

先定义回调。

public interface ProgressListener {    void update(String url, long bytesRead, long contentLength, boolean done);}

自定义 ProgressResponseBody。

public class ProgressResponseBody extends ResponseBody {    private final ResponseBody responseBody;    private final ProgressListener progressListener;    private BufferedSource bufferedSource;    private final String url;    ProgressResponseBody(String url, ResponseBody responseBody, ProgressListener progressListener) {        this.responseBody = responseBody;        this.progressListener = progressListener;        this.url = url;    }    @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(final 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;                progressListener.update(url, totalBytesRead, responseBody.contentLength(), bytesRead == -1);                return bytesRead;            }        };    }}

定义拦截器。从 Response 中获取信息。

public class ProgressInterceptor implements Interceptor {    private ProgressListener progressListener;    public ProgressInterceptor(ProgressListener progressListener) {        this.progressListener = progressListener;    }    @NotNull    @Override    public Response intercept(@NotNull Chain chain) throws IOException {        Response originalResponse = chain.proceed(chain.request());        return originalResponse.newBuilder()                .body(new ProgressResponseBody(chain.request().url().url().toString(), originalResponse.body(), progressListener))                .build();    }}
增加拦截器

在创立 OkHttpClient 时增加 ProgressInterceptor。

OkHttpClient okHttpClient = new OkHttpClient.Builder()            .connectTimeout(8, TimeUnit.SECONDS)            .addInterceptor(new ProgressInterceptor(new ProgressListener() {                @Override                public void update(String url, long bytesRead, long contentLength, boolean done) {                    // tellProgress(url, bytesRead, contentLength, done);                }            }))            .build();

值得注意的是这里的进度更新十分频繁。并不一定每次回调都要去更新UI。

【Android开发:框架源码解析视频参考】