天天看點

android 流量 壓縮

對于目前的狀況來說,移動終端的網絡狀況沒有PC網絡狀況那麼理想。在一個Android應用中,如果需要接收來自伺服器的大容量資料,那麼就不得不考慮客戶的流量問題。本文根據筆者的一個項目實戰經驗出發,解決大容量資料的互動問題,解決資料大小會根據實際情況動态切換問題(伺服器動态選擇是否要壓縮資料,用戶端動态解析資料是否是被壓縮的),還有資料互動的編碼問題。

解決資料過大的問題,最直覺的方法就是壓縮資料。伺服器将需要傳遞的資料先進行壓縮,再發送給Android用戶端,Android用戶端接收到壓縮的資料,對其解壓,得到壓縮前的資料。

如果規定Android用戶端和伺服器的互動資料必須是經過某種壓縮算法後的資料,那麼這種“規定”失去了視具體情況而定的靈活性。筆者拟将Http協定進行封裝,将動态的選擇傳輸的資料是否要經過壓縮,用戶端也能動态的識别,整理并獲得伺服器想要發送的資料。Android用戶端向伺服器請求某個方面的資料,這個資料也許是經過壓縮後傳遞比較合适,又也許是将原生資料傳遞比較合适。也就是說,筆者想要設計一種協定,這種協定适用于傳輸資料的資料量會動态的切換,也許它會是一個小資料,也許它又會是一個資料量龐大的大資料(大資料需要經過壓縮)。

可能說的比較抽象,那麼我用實際情況解釋一下。

我項目中的一個實際情況是這樣的:這個項目是做一個Android基金用戶端,Android用戶端向伺服器請求某一個基金的曆史走勢資訊,由于我的Android用戶端實作了本地緩存,這讓傳遞資料的大小浮動非常大。如果本地緩存的曆史走勢資訊的最新日期是5月5日,伺服器的曆史走勢資訊的最新日期是5月7日,那麼伺服器就像發送5月6日和5月7日這兩天的走勢資訊,這個資料很小,不需要壓縮(我使用的壓縮算法,對于資料量過小的資料壓縮并不理想,資料量過小的資料壓縮後的資料會比壓縮前的資料大)。然而,Android用戶端也可能對于某個基金沒有任何的緩存資訊,那麼伺服器将發送的資料将是過去三四年間的曆史走勢資訊,這個資料會有點大,就需要進行壓縮後傳遞。那麼用戶端對于同一個請求得到的資料,如何判斷它是壓縮後的資料還是未曾壓縮的資料呢?

筆者使用的解決方案是把傳遞資料的第一個位元組作為辨別位元組,将辨別這個資料是否被壓縮了。也能辨別傳遞資料的編碼問題。Android對于接收到的資料(位元組數組),先判斷第一個位元組的資料,就能根據它所代表的資料格式和編碼資訊進行相應的操作。說了那麼多,也許不如看實際的代碼了解的快。首先是壓縮算法,這裡筆者用到的是jdk自帶的zip壓縮算法。

這裡供外部調用的方法是byteCompress()和byteDecompress(),都将接收一個byte數組,byteCompress是資料壓縮方法,将傳回壓縮後的數組資料,byteDecompress是資料解壓方法,将傳回解壓後的byte數組資料。FLAG_GBK_STRING_COMPRESSED_BYTEARRAY表示伺服器傳遞的資料是GBK編碼的字元串經過壓縮後的位元組數組。其它的常量也能根據其名字來了解。(這裡多說一句,最好将編碼方式和是否壓縮的辨別位分開,比如将辨別位元組的前四個位定義成辨別編碼方式的位,将後面四個位辨別為是否壓縮或者其它資訊的辨別位,通過位的與或者或方式來判斷辨別位。筆者這裡偷懶了,直接就這麼寫了。)

下面是處理傳遞資料的方法(判斷是否要壓縮)。我這裡用要的是Struts 1架構,在Action裡組織資料,并作相應的處理(壓縮或者不壓縮),并發送。

這裡我預發送的資料是一個Json格式的字元串(GBK編碼),将判斷這個字元串的長度(判斷是否适合壓縮)。如果适合壓縮,就将緩沖位元組數組(ByteArrayOutputStream resultBuffer)的第一個位元組填充FLAG_GBK_STRING_COMPRESSED_BYTEARRAY,再将Json字元串的位元組數組壓縮,并存入資料緩沖位元組數組,最後向輸出流寫入緩沖位元組數組,關閉流。如果不适合壓縮,将發送的資料的第一個位元組填充為FLAG_GBK_STRING_UNCOMPRESSED_BYTEARRAY,再将Json字元串的位元組數組直接存入資料緩沖位元組數組,寫入輸出流,關閉流。

最後就是Android用戶端的解析了,将上述的Compress壓縮輔助類拷貝到Android項目中就行。下面是Http請求後得到的位元組數組資料做解析工作。(Android用戶端如何使用Http向伺服器請求資料請參考我前面的一篇部落格)。

這裡最後得到的result就是伺服器實際要發送的内容。

缺陷反思:任何設計都是有缺陷的。我這樣做已經将Http協定做了進一層封裝。Http的資料部分的第一個位元組并不是實際資料,而是辨別位元組。這樣,降低了這個接口的可重用性。統一發送Json字元串的Action能被網頁(Ajax)或者其他用戶端使用,經過封裝壓縮之後,隻有能識别這個封裝(就是能進行解析)的用戶端能使用這個接口。網頁(Ajax)就不能解析,那麼這個Action就不能被Ajax使用。

具體開發過程中要視具體情況而定,如果資料量小的話我還是建議使用标準的Http協定,也就是說直接發送字元串,不做任何的壓縮和封裝。如果資料量實在過于大的話,建議使用我上述的方法。

有博友問,對于Android應用來說,什麼樣的資料才算是大資料。我想這個大資料的界限并不是固定的,并不是說10k以上,或者100k以上就算是大資料,這個界限是由許多方面的利弊來衡量的。首先我要說,我設計的這個協定是适用于大資料和小資料動态切換的情況。對于大小資料界限的劃定,交給開發人員去衡量利弊。這個衡量标準我想應該包括以下幾部分内容:

第一,壓縮算法的有效臨界點。隻有要壓縮的資料大于這個點,壓縮後的資料才會更小,反之,壓縮後的資料會更加的大。我使用的zip算法這個點應該是50位元組左右,是以,在我應用中,将大資料定義成50位元組以上的資料。

第二:壓縮和解壓的開銷。伺服器要壓縮資料,用戶端要解壓資料,這個都是需要CPU開銷的,特别是伺服器,如果請求量大的話,需要為每一個響應資料進行壓縮,勢必降低伺服器的性能。我們可以設想這樣的一種情況,原生資料隻有50位元組,壓縮完會有40位元組,那麼我們就要思考是否有必要來消耗CPU來為我們這區區的10個位元組來壓縮呢?

綜上,雖然這個協定适合大小資料動态切換的資料傳輸,但是合理的選擇大資料和小資料的分割點(定義多少大的資料要壓縮,定義多少以下的資料不需要壓縮)是需要好好權衡的。