Android错误笔记---下载文件无法获取到文件(ContentLength)的大小
由于项目需要做APP自动更新的功能,即进入程序自动请求获取最新的APP版本号,判断是否需要更新,这里就要涉及到下载APP文件并显示安装界面提示用户安装程序,做过下载文件的同学都知道,为了提高用户体验,就要实现下载进度条的的功能,进度值一般都是0-100,而这个值都是经过代码计算的。首先请求下载文件时http响应中会返回ContentLength,即文件大小,我们再根据当前已下载的大小除以文件大小计算得到进度值。很简单的功能,在开发中却遇到很多问题,获取到的ContentLength值为-1。
使用的网络框架是retrofit+rxjava,下载文件的主要代码如下:
/**
* 异步下载文件
* @param baseUrl 地址
* @param url 文件地址
* @param path 存放路径
* @param fileName 文件名
* @param callback 回调
*/
public void downloadFileWithDynamicUrlSync(String baseUrl, String url, final String path, final String fileName, final DownloadCallback callback) {
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
LoggingInterceptor httpLoggingInterceptor = null;
if (BuildConfig.DEBUG) {
//debug模式下 打印网络请求日志
httpLoggingInterceptor = new LoggingInterceptor.Builder()
.loggable(BuildConfig.DEBUG)
.setLevel(Level.BASIC)
.log(Platform.INFO)
.request("请求")
.response("响应")
.build();
}
builder.addInterceptor(httpLoggingInterceptor);
Retrofit retrofit = new Retrofit.Builder().baseUrl(baseUrl)
.client(builder.build())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build();
ConnectService service = retrofit.create(ConnectService.class);
LogUtil.logNormalMsg("开始下载");
service.downloadFileWithDynamicUrlSync(url).subscribeOn(Schedulers.io()).
observeOn(Schedulers.io())
.subscribe(new Observer<ResponseBody>() {
@Override
public void onSubscribe(Disposable d) {
}
@Override
public void onNext(ResponseBody responseBody) {
InputStream inputStream = responseBody.byteStream();
writeFile(inputStream, responseBody, path, fileName, callback);
}
@Override
public void onError(Throwable e) {
callback.downloadFail("文件下载失败:" + e.getMessage());
}
@Override
public void onComplete() {
}
});
}
/**
* 将输入流写入文件
*
* @param inputString 写入流
* @param filePath 文件路径
* @param fileName 文件名
* @param callback 回调接口
*/
private static void writeFile(InputStream inputString, ResponseBody body, String filePath, String fileName, DownloadCallback callback) {
File file = new File(filePath + File.separator + fileName);
if (file.exists()) {
file.delete();
}
FileOutputStream fos = null;
try {
fos = new FileOutputStream(file);
byte[] b = new byte[1024];
int len;
long totalLength = body.contentLength();
Log.logNormalMsg("ConnectControl", "totalLength:" + totalLength);
long current = 0;
while ((len = inputString.read(b)) != -1) {
fos.write(b, 0, len);
current += len;
if (current != 0 && totalLength != 0) {
int progress = (int) (100 * current / totalLength);
callback.downloading(progress);
LogUtil.logNormalMsg("ConnectControl", "文件" + fileName + "已下载---" + (int) (100 * current / totalLength) + "%");
}
}
inputString.close();
fos.close();
} catch (FileNotFoundException e) {
LogUtil.logNormalMsg("文件写入失败---FileNotFoundException:" + e.getMessage());
callback.downloadFail("文件下载失败");
} catch (IOException e) {
LogUtil.logNormalMsg("文件写入失败---IOException:" + e.getMessage());
callback.downloadFail("文件下载失败");
}
callback.downloadSuccess(file.getAbsolutePath());
}
public void clear(){
if (service != null) {
service = null;
}
}
执行下载时获取的ContentLength为-1,网上搜索了下解决方法,都说设置请求头header的Accept-Encoding为identity就可以了,大概意思是设置返回的编码方式为本体,因为http会将大文件返回的编码方式默认为gzip,即压缩返回,这样就没法获取到ContentLength,接下来设置一下header头:
OkHttpClient.Builder builder = new OkHttpClient.Builder();
builder.readTimeout(10, TimeUnit.SECONDS);
builder.connectTimeout(9, TimeUnit.SECONDS);
builder.addInterceptor(new Interceptor() {
@Override
public Response intercept(Chain chain) throws IOException {
Request request = chain.request().newBuilder()
.addHeader("Accept-Encoding","identity").build();
return chain.proceed(request);
}
});
执行请求,但是结果还是一样,ContentLength为-1,最后在这篇文章才找到答案
http 响应头里 没有 或者有 content-length 的几种可能性
由于后台是将文件读取为文件流再将文件流返回给前端,这样的http响应默认的Transfer-Encoding为chunked,当Connection为keep-alive时,Transfer-Encoding和ContentLength只能二选一存在一个,所有前端是没法获取到ContentLength的,如下图:
这里的解决方法就是后台设置ContentLength的值即可:
//file为下载的文件
response.setContentLength((int) file.length());
再执行请求后前端可以看到ContentLength:
最后在调试时还发现一个情况,当后台设置的下载方式不是以读取文件再将文件流的方式返给前端,而是直接返回文件在服务器上的绝对路径,即:
http://你的ip/group1/M00/00/7E/CgEoa1z3hDyAcUcEAMRHXV-xJ4g661.apk
前端直接根据这个路径下载就能获取到ContentLength的值,估计是http自动识别并返回了ContentLength。
上一篇: android 获取MP4文件的图片大小