在 Android 中进行 GZIP 数据压缩,主要涉及向服务器发送压缩数据 和解压服务器返回的压缩数据两个场景。
以下是如何在客户端(Android)实现这两个关键操作的详细说明和代码示例。
一、发送数据:压缩请求体(POST/PUT等)
当你需要向服务器发送大量文本数据(如 JSON)时,在客户端压缩可以显著减少上传流量。
核心步骤:
- 压缩数据 :使用
GZIPOutputStream将原始数据(如字符串)压缩为字节数组。 - 设置请求头 :在 HTTP 请求头中明确告诉服务器数据是经过 GZIP 压缩的,通常设置
Content-Encoding: gzip。 - 发送二进制数据:将压缩后的字节数组作为请求体发送。
以下是使用 OkHttp 库发送 GZIP 压缩请求的推荐做法:
kotlin
import okhttp3.*
import okio.*
import java.io.ByteArrayOutputStream
import java.util.zip.GZIPOutputStream
// 1. 创建一个 OkHttpClient 实例
val client = OkHttpClient()
// 2. 准备要发送的原始数据(例如一个 JSON 字符串)
val jsonString = """{"name":"张三","age":25,"comment":"这是一个可能会很长的文本字段..."}"""
val requestBody = jsonString.toRequestBody("application/json; charset=utf-8".toMediaType())
// 3. 使用 GZIP 压缩请求体
val gzippedRequestBody = requestBody.gzip() // 调用下面的扩展函数
// 4. 构建请求,并设置正确的请求头
val request = Request.Builder()
.url("https://your.api.com/endpoint")
.post(gzippedRequestBody) // 使用压缩后的请求体
.header("Content-Encoding", "gzip") // 关键:声明内容编码方式
.build()
// 5. 发起异步请求
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// 处理网络失败
}
override fun onResponse(call: Call, response: Response) {
// 处理服务器响应
}
})
// --- 关键:用于压缩 RequestBody 的 OkHttp 扩展函数 ---
fun RequestBody.gzip(): RequestBody {
return object : RequestBody() {
override fun contentType(): MediaType? = this@gzip.contentType()
override fun contentLength(): Long = -1 // 压缩后长度未知,传递 -1
override fun writeTo(sink: BufferedSink) {
// 创建 GZIP 输出流,包装原始的 sink
val gzipSink = GZIPOutputStream(sink.outputStream()).buffer()
// 将原始 RequestBody 的内容写入压缩流
this@gzip.writeTo(gzipSink)
gzipSink.close() // 必须关闭 GZIP 流以写入尾部数据
}
}
}
二、接收数据:处理服务器返回的压缩响应(GET等)
更常见的场景是,服务器返回了经过 GZIP 压缩的响应体(如 HTML、JSON),客户端需要正确解压。好消息是,现代网络库(如 OkHttp)通常会自动处理这个过程。
方式一:依赖网络库自动处理(推荐)
OkHttp、Retrofit 等库默认会自动处理 Content-Encoding: gzip 响应头,并对响应体进行透明解压。 你通常不需要手动写解压代码。
kotlin
val client = OkHttpClient.Builder()
.build() // 默认已支持自动解压 GZIP 响应
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
client.newCall(request).enqueue(object : Callback {
override fun onResponse(call: Call, response: Response) {
// OkHttp 已在此处自动解压,你直接拿到的是解压后的字符串
val responseBodyString = response.body?.string()
// ... 处理数据
}
// ... onFailure
})
方式二:手动解压字节数据(如需)
如果你通过其他方式(如 HttpURLConnection)获得了原始的压缩字节流,可以手动解压。
kotlin
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.util.zip.GZIPInputStream
fun decompressGzip(compressedBytes: ByteArray): String {
// 使用 GZIPInputStream 包装压缩的字节输入流
ByteArrayInputStream(compressedBytes).use { bis ->
GZIPInputStream(bis).use { gzipIs ->
// 读取解压后的数据
ByteArrayOutputStream().use { bos ->
val buffer = ByteArray(1024)
var len: Int
while (gzipIs.read(buffer).also { len = it } != -1) {
bos.write(buffer, 0, len)
}
// 转换为字符串(假设是文本)
return bos.toString("UTF-8")
}
}
}
}
// 使用示例
// val compressedData: ByteArray = ... // 从网络获取的原始字节
// val originalString = decompressGzip(compressedData)
三、关键实践要点与注意事项
| 要点 | 说明与建议 |
|---|---|
| 1. 服务器协同 | 发送时:服务器需能解析 Content-Encoding: gzip 请求头并自动解压。 接收时:服务器需正确设置 Content-Encoding: gzip 响应头。 |
| 2. 压缩收益 | 文本数据 (JSON、XML、HTML)压缩效果极佳(可达70%+)。 已压缩格式(如图片JPEG/PNG、视频、已为.gz文件)再次压缩无效,反而增加开销。 |
| 3. 性能权衡 | 压缩/解压消耗 CPU 资源 ,节省网络带宽和传输时间 。在移动网络下,通常利远大于弊。这与耗电优化需权衡:密集压缩可能增加耗电,但大幅缩短了网络活跃时间,通常整体更省电。 |
| 4. 测试验证 | 使用抓包工具(如 Charles、Fiddler)检查请求/响应头及原始数据大小,确认压缩生效。在代码中打印压缩前后数据长度进行对比。 |
四、总结与最佳实践
- 发送压缩请求 :使用 OkHttp 等库的扩展功能,在发送前压缩请求体,并正确设置
Content-Encoding: gzip头。 - 接收压缩响应 :优先依赖网络库的自动解压功能,无需手动实现。
- 针对性压缩:主要对文本类 API 响应和请求进行压缩,避免对已压缩的二进制资源重复操作。
- 全程监控:通过抓包工具验证压缩效果,确保服务器和客户端配置正确。
将 GZIP 压缩与之前提到的网络请求合并、缓存策略、图片优化等手段结合,能全方位、有效地优化应用流量。