美文网首页
Android 网络框架解压缩(gzip)浅谈

Android 网络框架解压缩(gzip)浅谈

作者: Eric_feng | 来源:发表于2017-09-06 12:15 被阅读1098次

六谈这个话题,是因为很多时间都忽略了这个因素,网络传输数据的压缩很少有人去关注,然而有时间提到这个问题的时间却一时不知道怎么回答,或者已经忘掉了这个概念...

进入正题,首先来聊聊Gzip。

一、Gzip概念

Gzip是GNUZip的缩写,他是一个GNU自由软件的文件圧缩程序。

二、为什么要用Gzip

我们在进行网络传输数据时,经常用到json、xml等格式的数据,这些数据在传输前可以进行压缩,这时候就会涉及到一种压缩格式—Gzip。Gzip的压缩比率非常大,有的甚至能达到99.9%以上,可以大大减少传输内容,提高用户的传输速度,进而提高用户的体验。

三、检测是否使用Gzip压缩以及压缩比例

http://tool.chinaz.com/Gzips/

https://gzip.51240.com/

比如我们通过第一个链接看一下“开源中国的新闻页”,网址如下:

http://www.oschina.net/action/api/news_list?catalog=1&pageIndex=0&pageSize=20

压缩结果

结果显示,这个网页没有进行压缩,源文件大小为12KB,而压缩后,文件可减小到0.01KB,可以节省99.92%的传输控件。这是什么概念呢?相当于100MB的数据经过压缩后不到1MB。

四、Android中实现Gzip压缩的原理

说道这里,我们先说一下Http中的Gzip技术细节

HTTP协议上的GZIP编码是一种用来改进WEB应用程序性能的技术。一般服务器中都安装有这个功能模块的,服务器端不需做改动,当浏览器支持gzip 格式的时候, 服务器端会传输gzip格式的数据。具体讲就是 http request 头中 有 "Accept-Encoding", "gzip" ,response 中就有返回头Content-Encoding=gzip ,我们现在从浏览器上访问玩啥网站都是gzip格式传输的。

同样的的道理,我们可以在android 客户端 request 头中加入 "Accept-Encoding", "gzip" ,来让服务器传送gzip 数据。

首先,客户端发请求给服务端,会带上请求头:Accept-Encoding:gzip。第二步,服务端接收到请求头后,可以选择压缩或不压缩。第三步,服务端选择压缩后,文件明显变小,同时在响应头加上Content-Encoding:gzip。第四步,客户端接收到响应后,根据响应头中是否带有Content-Encoding:gzip,判断文件是否被压缩,如果压缩就进行解压,如果没有压缩,就按照正常方式读取数据即可。

五、在Android各网络框架中表现有什么差异

OKhttp

OKhttp3.4.0开始将这些逻辑抽离到了内置的interceptor中,看起来较为方便

BridgeInterceptor.java这个类里边可以看到

// If we add an "Accept-Encoding: gzip" header field we're responsible for also decompressing // the transfer stream. boolean transparentGzip = false; if (userRequest.header("Accept-Encoding") == null) {       transparentGzip = true;       requestBuilder.header("Accept-Encoding", "gzip"); }

如果header中没有Accept-Encoding,默认自动添加 ,且标记变量transparentGzip为true。

if (transparentGzip && "gzip".equalsIgnoreCase(networkResponse.header("Content-Encoding")) && HttpHeaders.hasBody(networkResponse)){

       GzipSource responseBody = new GzipSource(networkResponse.body().source());

       Headers strippedHeaders = networkResponse.headers().newBuilder()

       .removeAll("Content-Encoding")

       .removeAll("Content-Length")

       .build();

       responseBuilder.headers(strippedHeaders);

       responseBuilder.body(new RealResponseBody(strippedHeaders, Okio.buffer(responseBody)));

}

针对返回结果,如果同时满足以下三个条件:

1.transparentGzip为true,即之前自动添加了Accept-Encoding

2.header中标明了Content-Encoding为gzip

3.有body

移除 Content-Encoding、Content-Length,并对结果进行解压缩。

可以看到以上逻辑完成了,由此我们通过OkHttp源码得出以下结论:

1.开发者没有添加Accept-Encoding时,自动添加Accept-Encoding: gzip

2.自动添加的request,response支持自动解压

3.手动添加不负责解压缩

4.自动解压时移除Content-Length,所以上层Java代码想要contentLength时为-1

5.自动解压时移除 Content-Encoding

6.自动解压时,如果是分块传输编码,Transfer-Encoding: chunked不受影响。

HttpUrlConnection

由于引用太多源码就不写了,直接针对以上6点做结果分析

1.2.3后默认是gzip,不加Accept-Encoding会被自动添加上Accept-Encoding: gzip。

2.自动添加的request,response支持自动解压

3.手动添加不会负责解压缩。

4.这里提出一点HttpURLConnection 在Android 4.4以后底层是由OkHttp实现的,所以

      *4.4之后的版本,Content-Length被移除,getContentLength() = -1

      *2.3- 4.3之间,Content-Length 没有移除,getContentLength() = compressed size

5. 自动解压时的Content-Encoding

与Content-Length对应:

      *4.4之后的版本,Content-Encoding被移除

     *2.3- 4.3之间,Content-Encoding存在,无变化。

6. 自动解压时的分块编码传输

与OkHttp相同,Transfer-Encoding: chunked不受影响。

六、具体代码实例看下解压流程

private String getJsonStringFromGZIP(HttpResponse response) {

       String jsonString = null;

       try {

               InputStream is = response.getEntity().getContent();

               BufferedInputStream bis = new BufferedInputStream(is);

               bis.mark(2);

              // 取前两个字节

              byte[] header = new byte[2];

              int result = bis.read(header);

              // reset输入流到开始位置

             bis.reset();

             // 判断是否是GZIP格式

            int headerData = getShort(header);

           // Gzip 流 的前两个字节是 0x1e8b

          if (result != -1 && headerData == 0x1e8b) { LogUtil.d("HttpTask", " use GZIPInputStream  ");

                  is = new GZIPInputStream(bis);

          } else {

                  LogUtil.d("HttpTask", " not use GZIPInputStream");

                  is = bis;

         }

         InputStreamReader reader = new InputStreamReader(is, "utf-8");

         char[] data = new char[100];

         int readSize;

         StringBuffer sb = new StringBuffer();

         while ((readSize = reader.read(data)) > 0) {

                 sb.append(data, 0, readSize);

         }

                jsonString = sb.toString();

               bis.close();

               reader.close();

        } catch (Exception e) {

                LogUtil.e("HttpTask", e.toString(),e);

       }

       LogUtil.d("HttpTask", "getJsonStringFromGZIP net output : " + jsonString );

        return jsonString;

}

private int getShort(byte[] data) {

         return (int)((data[0]<<8) | data[1]&0xFF);

}

参考资料

         Android’s HTTP Clients

         HttpURLConnection

         HTTP 协议中的 Transfer-Encoding

相关文章

网友评论

      本文标题:Android 网络框架解压缩(gzip)浅谈

      本文链接:https://www.haomeiwen.com/subject/qjcyxxtx.html