Android项目网络请求支持Brotli压缩记录

什么是 Brotli 压缩

Brotli Google 官方库

Brotli 是 Google 推出的一种无损压缩算法,通过变种的LZ77算法、Huffman编码以及二阶文本建模等方式进行数据压缩,与其他压缩算法相比(如zip,gzip等),无论是压缩时间,还是压缩体积上看,它都有着更高的效率。

Brotli 如此高的压缩比率,得益于其使用一个预定义的字典,该字典包含超过 13000 个来自文本和 HTML 文档的大型语料库的常用字符串,预定义的算法可以提升较小文件的压缩密度,而压缩与解压缩速度则大致不变。

Brotli 特点和注意点:

  • 对比 gzip 算法,压缩性能更好,性能提升约15%~25%
  • 对 1kb 以内的文件不需要压缩,因为压缩前后差距不大
  • 几乎所有主流浏览器都已支持 br 算法,可以通过这里查询支持情况
  • 对于图片类型文件(PNG、JPG、JPEG等)和视频类型文件(MP4、AVI、WMV等)已经做了内容的压缩处理,无需 gzip 或 br 压缩,因为压缩后文件反而可能会更大
  • Brotli压缩支持的文件类型有 text/xml、text/plain、text/css、application/javascript、application/x-javascript、application/rss+xml、text/javascript、image/tiff、image/svg+xml、application/json、application/xml

Android 项目如何使用 Brotli

对于 web 和 ios 端,在浏览器和系统层已经开始支持 br 的自动解压了,但 Android 系统不会自动解析 br 数据,需要在应用层自己去实现。

Brotli 大部分由 C/C++ 写成,虽然 Brotli 库包含了 Java 模块,但它只是一个描述定义,还没有真正的 Java Brotli 编码器实现(具体可以查看 issure 405),那么如何在 Android Java 项目中使用,就需要我们自己思考了。

虽然我在 Okhttp 中发现了 BrotliInterceptor 的拦截器,但它使用的依然是是 Brotli 库的 Java 描述,同样没有真正的 Java 编码器实现,所以无法真正的解压 br 压缩后的数据:
在这里插入图片描述
👆BrotliInterceptor 也只有 Java 层的使用描述,而没有 C/C++ 的底层实现。

那么要在 Java 层使用 Brotli ,只能通过 JNI 方式来调用 C/C++ 的实现了,步骤如下:

  • 首先需要将 Brotli 库通过 CMake 工具(其他构建方式在Brotli首页有列出,如Bazel)构建出对应的 so 文件(Android 平台通常只需要包括 armeabi-v7a、arm64-v8a、x86、x86-64)
  • 新建 JNI 项目,定义 Java 层编解码调用逻辑,测试与 C/C++ 层交互
  • 最后将 Java 定义封装成 Jar ,与 so 库一同依赖到 Android 工程即可(也可以封装成 AAR)
  • 同时 Android 项目中的 Okhttp 的拦截器也需要自己去实现。

CMark 构建比较麻烦,最后还是在这个库: Brotli:https://github.com/chfdeng/Brotli 中找到已经封装好的 Brotli 压缩实现,不过它是把整个 Brotli 封装成 aar 库提供使用,我抽取了其中用到的CPU平台 so 库和 Jar 包,直接用到项目中。

真正应用到 Android 项目

我们项目网络层使用 Okhttp + Retrofit 实现,在没有任何处理情况下,首先通过抓包查看一下项目中的网络请求情况:

在这里插入图片描述

可以看到目前使用的是 gzip 压缩算法,其实这是 Okhttp 为我们默认添加的,这部分代码在 BridgeInterceptor 拦截器中:
在这里插入图片描述
如果没有指定 Accept-Encoding(传输编码) 和 Range(传输范围) 请求头,Okhttp 的 BrideInterceptor 拦截器就会默认添加 gzip 作为传输格式,而且在得到服务器响应时,如果响应体使用了 gzip 压缩( Accept-Encoding=gzip),就会默认解压,这也算 Okhttp 的一种优化。
在这里插入图片描述
由于我们服务器暂不支持gzip压缩,所以对于项目中使用 gzip 和 br 的对比也就暂时不做了,不过已经有人做了这样的对比,感兴趣可以去看这里

添加 br 压缩请求头:Accept-Encoding:br

如果想让服务端返回 br 压缩后的数据,那么可以通过添加请求头的方式告知服务端。但如果只对项目中请求头增加了 Accept-Encoding:br 而不做其他处理,那么会得到下面的报错(前提是服务端做了 br 压缩,也就是响应头中有:Content-Encoding:br):

报错1:Use JsonReader.setLenient(true) to accept malformed JSON at line 1 column 1 path $

报错2:com.google.gson.JsonSyntaxException: java.lang.IllegalStateException: Expected BEGIN_OBJECT but was STRING at line 1 column 1 path $

上面两个错误,其实都是因为没对 br 压缩后的数据做解压,导致 JSON 去解析乱码导致的,不信可以把 response body 打印出来看:
在这里插入图片描述
这些乱码就是压缩后的数据。

解压 br 压缩后的数据

在得到响应时,判断服务端是否做过压缩应该以 Content-Encoding 响应头为准,有这个响应头,且值为 br 说明需要进行 br 解压;值为 gzip 说明需要 gzip 解压。

现在把 Brotli 压缩相关的 so 库和 jar 文件添加到依赖项目,并通过 BrotliInputStream 对上面的 Response 进行解压:
在这里插入图片描述

这样就得到正常的响应了。

我们可以把这层逻辑进一步封装成 Okhttp 的一个拦截器:

object BrotliHeadInterceptor : Interceptor {

  override fun intercept(chain: Interceptor.Chain): Response =
    if (chain.request().header("Accept-Encoding") == null) {
      //客户端支持br和gzip
      val request = chain.request().newBuilder().header("Accept-Encoding", "br,gzip").build()

      val response = chain.proceed(request)

      uncompress(response)
    } else {
      chain.proceed(chain.request())
    }

  internal fun uncompress(response: Response): Response {
    if (!response.promisesBody()) {
      return response
    }

    val body = response.body ?: return response
    val encoding = response.header("Content-Encoding") ?: return response

    val decompressedSource = when {
      //br需要额外so库
      encoding.equals("br", ignoreCase = true) -> BrotliInputStream(body.source().inputStream())
        .source().buffer()
      //okhttp自动支持gzip
      encoding.equals("gzip", ignoreCase = true) -> GzipSource(body.source()).buffer()
      else -> return response
    }

    return response.newBuilder().removeHeader("Content-Encoding").removeHeader("Content-Length")
      .body(decompressedSource.asResponseBody(body.contentType(), -1)).build()
  }
}

在 Okhttp 构建时,将这个拦截器加入到拦截器链,即可支持 gzip 和 br。

启用br压缩后对比

这里只通过抓包工具进行直观对比:

启用前:
启用后:

总结

压缩的时间是固定的,数据在网络中传输的路径和时间是不定的,在算法中常量在一定情况下可以忽略(映射过来,就比如数据小于1kb或网络是直连的,这时 br 压缩收益很小)。所以对网络数据的压缩可谓一举多得:不仅能加快数据传输时间,减少带宽占用,优化了用户流量消耗,甚至能降低设备电池使用量。

本文中用到的 Brotli 所有支持(jar,so文件以及封装好的Okhttp拦截器),已经上传到CSDN,可以通过下面链接下载:
https://download.csdn.net/download/weixin_39397471/86273500

参考

Brotli Github 地址

Brotli 算法支持情况:http://caniuse.com/#feat=brotli

阿里Brotli压缩:https://help.aliyun.com/document_detail/120511.html

Brotli —— 下一代的 HTTP 服务器压缩

https://toutiao.io/posts/ei3sk4/preview

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值