简介

最近得到一个问题,之前事务上为了视频在发动APP后能秒播,对第一个视频做了预加载逻辑。可是最新发现,这个文件的预下载耗费用户的流量远远多于应该的预下载巨细。

开端抓包看,没有看出什么问题,由于在下载后读输入流的逻辑中是有只写入1/10流数据到缓存文件的逻辑的,可是在后来经过HttpCanary抓包的进程,真的接口呼应的巨细远远大于1/10的写文件巨细(比方缓存文件是1M,但抓包服务端呼应了4M,也便是耗费了用户4M的流量),但去看缓存的预下载视频文件,巨细又是吻合读1/10的文件巨细逻辑的。

剖析

简单剖析

下载逻辑中,header中并没有增加Range,也便是说是恳求的完好的视频文件,可是在输入流读取的时分,做了判断,如果大于文件巨细的1/10,则终止读取,并关闭输入流。
输入流是恳求服务端文件下载后,和服务端建立起来的一个数据通道,服务器和客户端之间经过这个管道做数据传输,可是问题来了,并不是客户端读取多少数据,服务端就刚好传输这么多数据。能够想见,服务端传输的数据必定是大于客户端承受的数据,否则,这个管道就没水了。

问题考虑

如果是完好的恳求一个视频地址文件的下载(不增加Range字段恳求文件片段的话),InputStream从服务器接纳数据的输入流读取的字节巨细和终究网络恳求耗费的网络巨细是无关的。
也便是说,InputStream你或许只读取了1MB的数据,可是整个下载网络恳求耗费的流量或许已经4MB了。能够这样理解这个问题:

InputStream是用于从服务器接纳数据的输入流。当你向服务器建议恳求时,服务器会处理恳求并回来相应的数据。这些数据被封装在呼应中,你经过InputStream从呼应中读取数据。

InputStream和服务器发送数据之间的关系能够类比为“水管”和“水流”的关系。服务器是“水源”,它发生数据并经过网络发送给你的运用。而InputStream是你的运用内部的“水管”,它担任接纳并传输这些数据,使你的运用能够获取服务器回来的信息。

当你开端读取InputStream时,数据会从网络经过“水管”传送到你的运用中,然后你的运用能够对数据进行处理,比方写入文件、解析JSON等操作。这个进程是由网络协议(如HTTP、HTTPS等)担任的,而数据的传输则会耗费网络流量

当你运用 InputStream 读取网络呼应数据时,实际读取的数据量并不会影响服务器呼应的巨细。服务器的呼应是提前生成的,不会由于你读取的数据量而改动。

总的来说,服务器的呼应巨细与你实际读取的数据量是独立的。服务器发送完好的呼应,而你从中读取数据时只是逐步接纳和处理呼应体的内容。服务器呼应巨细或许会遭到网络传输协议、头部信息、数据压缩等要素的影响,这些要素导致服务器的呼应巨细或许比你实际读取的数据量大。

解决方案

在预下载视频前,先经过HEAD恳求得到完好文件的巨细。再核算得到预下载的视频巨细,比方1/10,假如是一个10MB的文件,这儿核算得到的下载文件片段巨细便是1MB=110241024=1048575字节。
再进行文件下载恳求,HEAD中增加Range字段,加上视频片段的区间,比方这儿就应该是
Range: bytes=0-1048575

此时服务端回来的就只会包括这个区间的文件片段,耗费的流量也只会是文件片段巨细的流量。所以问题就解决了。

其他留意

事务逻辑上需要运用HEAD恳求得到的文件巨细,来做后续的核算,而不能再是文件GET恳求中回来的片段的Content-Length,由于此时也是片段的Content-Length,即1/10的文件巨细。

HEAD恳求获取原始文件巨细

Call headRequestCall = HttpConnect.getInstance().buildHeadCall(downloadUrl);
Response headResponse = headRequestCall.execute();
long fileSize = 0;
if (headResponse != null && headResponse.isSuccessful()) {
    String contentLength = headResponse.header("Content-Length");
    if (!TextUtils.isEmpty(contentLength)) {
        fileSize = Long.parseLong(contentLength);
    }
}
if (headResponse != null) {
    headResponse.close();
}
return fileSize;

核算预下载片段文件巨细

比方按照文件的1/10来核算,则核算后的巨细为

long limit = fileSize / 10;

恳求片段文件

这儿留意rangeEnd的值,这儿采用数组下标的规矩,所以rangeEnd=rangeStart+limit-1。

requestBuilder.addHeader("Range", "bytes=" + rangeStart + "-" + rangeEnd);

结束

终究,经过HttpCanary抓包,能够看到,服务端呼应的巨细和客户端恳求的Range长度是完全一致的,不会再存在耗费用户剩余流量的问题。