Android OkHttp + Retrofit 下载文件与进度监听
下载文件是一个比较常见的需求。给定一个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 下载文件与进度监听的更多相关文章
- 一行代码实现Okhttp,Retrofit,Glide下载上传进度监听
https://mp.weixin.qq.com/s/bopDUFMB7EiK-MhLc3KDXQ essyan 鸿洋 2017-06-29 本文作者 本文由jessyan投稿. jessyan的博客 ...
- Android OkHttp文件上传与下载的进度监听扩展
http://www.loongwind.com/archives/290.html 上一篇文章介绍了用Retrofit实现文件的上传与下载,但是我们发现没办法监听上传下载的进度,毕竟我们在做开发的时 ...
- Android OkHttp + Retrofit 断点续传
本文链接 前面我们已经知道如何使用OkHttp+Retrofit下载文件. 下载文件时,可能会遇到一些意外情况,比如网络错误或是用户暂停了下载. 再次启动下载,如果又要从头开始,会白白浪费前面下载好的 ...
- RxHttp 完美适配Android 10/11 上传/下载/进度监听
1.前言 随着Android 11的正式发布,适配Android 10/11 分区存储就更加的迫切了,因为Android 11开始,将强制开启分区存储,我们就无法再以绝对路径的方式去读写非沙盒目录下的 ...
- 教你如何在 Android 使用多线程下载文件
# 教你如何在 Android 使用多线程下载文件 前言 在 Android 日常开发中,我们会经常遇到下载文件需求,这里我们也可以用系统自带的 api DownloadManager 来解决这个问题 ...
- Android利用Http下载文件
Android利用Http下载文件 一.场景 下载存文本文件和下载如mp3等大容量的文件 界面 二.代码编写 1.AndroidMainfest.xml中配置 主要是解决网络权限和写SDCard的权限 ...
- VC下载文件显示进度条
VC下载文件显示进度条 逗比汪星人2009-09-18上传 by Koma http://blog.csd.net/wangningyu http://download.csdn.net/deta ...
- Android 通过SOCKET下载文件的方法
本文实例讲述了Android通过SOCKET下载文件的方法.分享给大家供大家参考,具体如下: 服务端代码 import java.io.BufferedInputStream; import java ...
- 文件下载Controller,文件夹内容监听,文件上传,运行程序通过url实现文件下载
文件下载Controller @RequestMapping("/fileDownLoad") public ResponseEntity<byte[]> fileDo ...
随机推荐
- 如何在Centos服务器上搭建起Oracle10、VNC、以及FTP
一.重装和分区 1.配置所需磁盘阵列(Raid): 2.正确分区: 3.Centos安装:过于简单,请自行bd. 二.连网 系统安装完成之后,我们需为其分配IP和DNS: "编辑连接&quo ...
- 由于找不到opencv_world***d.dl,无法继续执行代码。重新安装程序可能会解决此问题。关于opencv使用imshow函数闪退解决方法等问题
1.将缺失的文件放到程序根目录的debug中 2.将Debug x64下的附加依赖项改为只有后缀为d.lib的那个库文件,去除另一个,问题就这么解决了,虽然我也不知道加上另一个为什么就会闪退
- 第五场周赛(字符串卡常个人Rank赛)——题解
本次题目因为比较简单,除了个别题目,其余题目我只写一个思路不再贴代码. 先是Div.2的题解 A题奇怪的优化,把递归函数改成2*fun(...)即可,其实看懂程序也不难,就是求a*2b: B题你会st ...
- 《即时消息技术剖析与实战》学习笔记7——IM系统的消息未读
一.什么是消息未读 消息未读包括会话未读和总未读.前者指的是当前用户和某一聊天方的未读消息数,后者指的是当前用户的所有未读消息数,也就是所有会话未读的和.比如用户A收到用户B的2条消息,还收到用户C的 ...
- Apache Commons Collections 反序列化详细分析学习总结
0x01.环境准备: Apache Commons Collections 3.1版本,下载链接参考: https://www.secfree.com/a/231.html jd jui地址(将jar ...
- ubuntu系统设置开机后使用使用终端运行应用程序
1.在菜单栏输入start,点击startup application进入 2. 添加开机启动程序 3.因为要用终端启动一个应用程序,所以command输入:gnome-terminal -- 程序执 ...
- 让API实现版本管理的实践
API版本管理的重要性不言而喻,对于API的设计者和使用者而言,版本管理都有着非常重要的意义.下面会从WEB API 版本管理的角度提供几种常见办法: 首先,对于API的设计和实现者而言,需要考虑向后 ...
- Kubernetes学习之应用部署变迁
从物理单机.虚拟化(容器化)到云原生 历史 云原生 ---初期 总结
- mysql创建表时字段类型选择与优化
一.选择原则 1.应该尽量使用可以正确存储数据的最小字段类型 2.选用简单的数据类型,例如:一个是尽量用mysql内置的字段类型来存储日期和时间:另一个存储IP地址尽量用整型:能用整型的尽量不用字符串 ...
- Spring Boot 的单元测试和集成测试
学习如何使用本教程中提供的工具,并在 Spring Boot 环境中编写单元测试和集成测试. 1. 概览 本文中,我们将了解如何编写单元测试并将其集成在 Spring Boot 环境中.你可在网上找到 ...