本文链接

下载文件是一个比较常见的需求。给定一个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. sql 多行、一行 互转

    原始数据: 期望数据: IF OBJECT_ID('temp_20170701','u') IS NOT NULL DROP TABLE temp_20170701 CREATE TABLE temp ...

  2. '\b'退格符号笔记

    今天在给小孩儿讲for循环输出最后一个输出项没有空格的情况 借助标记,选择在第二个至最后一个的输出项前添加空格 ; ; i < n; i++) { ) cout << a[i]; e ...

  3. 基于python-django框架的支付宝支付案例

    目录 @ 一. 开发前的准备 1. 必须了解的知识 SDK:软件开发工具包,可以为开发者提供快速开发的工具 沙箱环境:也就是测试环境 支付宝支付金额的精度:小数点后两位(面试) 支付宝用的什么加密方式 ...

  4. oracle 11g 下载安装 使用记录

    Oracle 11g 使用记录 1.下载oracle快捷安装版:   (1)下载连接:https://pan.baidu.com/s/1ClC0hQepmTw2lSJ2ODtL7g 无提取码 (2)去 ...

  5. [Spark] 00 - Install Hadoop & Spark

    Hadoop安装 Java环境配置 安装课程:安装配置 配置手册:Hadoop安装教程_单机/伪分布式配置_Hadoop2.6.0/Ubuntu14.04[依照步骤完成配置] jsk安装使用的链接中第 ...

  6. HashMap浅析

    一.概述 HashMap,基于哈希结构的Map接口的一个实现,无序,允许null键值对,线程不安全的.可以使用集合工具类Collections中的synchronizedMap方法,去创建一个线程安全 ...

  7. python的pywinrm模块远程连接windows执行dos命令

    ----A机器远程连接B---- 在A机器上安装模块: pip install pywinrm 在B机器上配置winrm服务的相关配置,使其支持远程控制: (winrm服务是windows 一种方便远 ...

  8. 报错:ORA-25150:不允许对区参数执行ALERING

    alter table 表名 MOVE   storage   (     next 128   ) ; -- Add/modify columns alter table 表名 add 列名 var ...

  9. 在Debian上用FVWM做自己的桌面

    用FVWM做自己的桌面 Table of Contents 1. 前言 2. 学习步骤 3. 准备 3.1. 软件包 3.2. 字体 3.3. 图片 3.4. 参考资料 4. 环境 5. 布局 6. ...

  10. centos 升级

    yum -y update升级所有包同时也升级软件和系统内核 yum -y upgrade只升级所有包,不升级软件和系统内核