下载文件是一个比拟常见的需要。给定一个 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 开发:框架源码解析视频参考】