六談這個話題,是因為很多時間都忽略了這個因素,網絡傳輸數據的壓縮很少有人去關注,然而有時間提到這個問題的時間卻一時不知道怎么回答,或者已經忘掉了這個概念...
進入正題,首先來聊聊Gzip。
一、Gzip概念
Gzip是GNUZip的縮寫,他是一個GNU自由軟件的文件圧縮程序。
二、為什么要用Gzip
我們在進行網絡傳輸數據時,經常用到json、xml等格式的數據,這些數據在傳輸前可以進行壓縮,這時候就會涉及到一種壓縮格式—Gzip。Gzip的壓縮比率非常大,有的甚至能達到99.9%以上,可以大大減少傳輸內容,提高用戶的傳輸速度,進而提高用戶的體驗。
三、檢測是否使用Gzip壓縮以及壓縮比例
比如我們通過第一個鏈接看一下“開源中國的新聞頁”,網址如下:
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