欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Android错误笔记---下载文件无法获取到文件(ContentLength)的大小

程序员文章站 2022-05-23 15:17:53
...

由于项目需要做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的,如下图:
Android错误笔记---下载文件无法获取到文件(ContentLength)的大小
这里的解决方法就是后台设置ContentLength的值即可:

//file为下载的文件
response.setContentLength((int) file.length());

再执行请求后前端可以看到ContentLength:
Android错误笔记---下载文件无法获取到文件(ContentLength)的大小
最后在调试时还发现一个情况,当后台设置的下载方式不是以读取文件再将文件流的方式返给前端,而是直接返回文件在服务器上的绝对路径,即:

http://你的ip/group1/M00/00/7E/CgEoa1z3hDyAcUcEAMRHXV-xJ4g661.apk

前端直接根据这个路径下载就能获取到ContentLength的值,估计是http自动识别并返回了ContentLength。