本文链接

下载文件是一个比较常见的需求。给定一个url,我们可以使用URLConnection下载文件

使用OkHttp也可以通过流来下载文件。

给OkHttp中添加拦截器,即可实现下载进度的监听功能。

使用流来实现下载文件

代码可以参考:https://github.com/RustFisher/android-Basic4/tree/master/appdowloadsample

获取并使用字节流,需要注意两个要点,一个是服务接口方法的 @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 OkHttp + Retrofit 下载文件与进度监听的更多相关文章

  1. 一行代码实现Okhttp,Retrofit,Glide下载上传进度监听

    https://mp.weixin.qq.com/s/bopDUFMB7EiK-MhLc3KDXQ essyan 鸿洋 2017-06-29 本文作者 本文由jessyan投稿. jessyan的博客 ...

  2. Android OkHttp文件上传与下载的进度监听扩展

    http://www.loongwind.com/archives/290.html 上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时 ...

  3. Android OkHttp + Retrofit 断点续传

    本文链接 前面我们已经知道如何使用OkHttp+Retrofit下载文件. 下载文件时,可能会遇到一些意外情况,比如网络错误或是用户暂停了下载. 再次启动下载,如果又要从头开始,会白白浪费前面下载好的 ...

  4. RxHttp 完美适配Android 10/11 上传/下载/进度监听

    1.前言 随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的 ...

  5. 教你如何在 Android 使用多线程下载文件

    # 教你如何在 Android 使用多线程下载文件 前言 在 Android 日常开发中,我们会经常遇到下载文件需求,这里我们也可以用系统自带的 api DownloadManager 来解决这个问题 ...

  6. Android利用Http下载文件

    Android利用Http下载文件 一.场景 下载存文本文件和下载如mp3等大容量的文件 界面 二.代码编写 1.AndroidMainfest.xml中配置 主要是解决网络权限和写SDCard的权限 ...

  7. VC下载文件显示进度条

    VC下载文件显示进度条 逗比汪星人2009-09-18上传   by Koma http://blog.csd.net/wangningyu http://download.csdn.net/deta ...

  8. Android 通过SOCKET下载文件的方法

    本文实例讲述了Android通过SOCKET下载文件的方法.分享给大家供大家参考,具体如下: 服务端代码 import java.io.BufferedInputStream; import java ...

  9. 文件下载Controller,文件夹内容监听,文件上传,运行程序通过url实现文件下载

    文件下载Controller @RequestMapping("/fileDownLoad") public ResponseEntity<byte[]> fileDo ...

随机推荐

  1. Angular 使用教程

    1.下载node.js,然后一直安装,可以修改一下node.js文件安装路径 查看是否node.js安装成功,在运行——cmd中输入以下代码.如果安装成功,则会显示出node.js的版本号 node ...

  2. Go依赖管理及Go module使用

    Go语言的依赖管理随着版本的更迭正逐渐完善起来. 依赖管理 为什么需要依赖管理 最早的时候,Go所依赖的所有的第三方库都放在GOPATH这个目录下面.这就导致了同一个库只能保存一个版本的代码.如果不同 ...

  3. Linux下PHP+Nginx环境搭建

    PHP+Nginx环境搭建 作者:王宇阳( Mirror )^_^ 参考文章: ​ Nginx+PHP+MySQL安装参考 ​ PHP源码安装经验 ​ PHP源码环境搭建过程中常见问题 CentOS环 ...

  4. 一文看懂java的IO流

    废话不多说,直接上代码 import com.fasterxml.jackson.databind.ObjectMapper; import java.io.*; import java.nio.ch ...

  5. redis.windows.conf配置详解

    redis.windows.conf配置详解 转自:https://www.cnblogs.com/kreo/p/4423362.html # redis 配置文件示例 # 当你需要为某个配置项指定内 ...

  6. 为什么有的编程规范要求用 void 0 代替 undefined

    Undefined Undefined 类型表示未定义,它的类型只有一个值,就是 undefined. 任何变量在被赋值前它的值都是 undefined,但是在 JavaScript 引擎中,unde ...

  7. 微软发布.Net Core 3.0 RC1,最终版本定于9月23日

    2019.9.17 微软 宣布推出.NET Core 3.0 Release Candidate 1.就像Preview 9一样,主要专注于为 .NET Core 3.0 发布最终版本 .现在变得非常 ...

  8. [翻译] ASP.NET Core 3.0 的新增功能

    ASP.NET Core 3.0 的新增功能 全文翻译自微软官方文档英文版 What's new in ASP.NET Core 3.0 本文重点介绍了 ASP.NET Core 3.0 中最重要的更 ...

  9. AppScan工具使用-实战一

    本文首发于简书https://www.jianshu.com/p/639cf894838e 工具已经安装完成,废话不多说,直接拿手上的项目使用一下. 1.打开工具,点击文件-新建 2.打开新建扫描 3 ...

  10. jquery的api以及用法总结-属性/css/位置

    属性/css 属性 .attr() attr()设置普通属性,prop()设置特有属性 获取或者设置匹配的元素集合中的第一个元素的属性的值 如果需要获取或者设置每个单独元素的属性值,需要依靠.each ...