Android WebP 圖片壓縮與傳輸
1. 簡介
直到4g時代,流量依然是寶貴的東西。而移動網(wǎng)絡(luò)傳輸中,最占流量的一種載體:圖片,成為了我們移動開發(fā)者不得不關(guān)注的一個問題。
我們關(guān)注的問題,無非是圖片體積和質(zhì)量如何達到一個比較和諧的平衡,希望得到質(zhì)量不錯的圖片同時體積還不能太大。
走在時代前列的谷歌給出了一個不錯的答案——WebP。
WebP是一種圖片文件格式,在相同的壓縮指標(biāo)下,webp的有損壓縮能比jpg小 25-34%。而在我自己的測試里,有時候能小50%。
2. 大企業(yè)背書
WebP在2010年發(fā)布第一個版本,到現(xiàn)在已經(jīng)6年了,谷歌旗下的各種網(wǎng)站G+、以及非常有代表性的YouTube,他的視頻文件格式WebM就是基于WebP構(gòu)造的。
據(jù)說騰訊、淘寶、美團也有部分應(yīng)用。
3. Android 端 JPG 轉(zhuǎn)換 WebP
RxJava線程轉(zhuǎn)換:
String[] imgs = new String[]{"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"};
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/test/";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// test = Api.getBuilder().create(Test.class);
String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE
, Manifest.permission.READ_PHONE_STATE
, Manifest.permission.CAMERA};
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
requestPermissions(permissions, 0);
}
compress();
}
private void compress() {
Observable.from(imgs)
.subscribeOn(Schedulers.io())
.doOnNext(new Action1<String>() {
@Override
public void call(String imgName) {
compress(imgName);
}
})
.subscribe();
}
private void compress(String imgName) {
try {
File file = new File(path, imgName);
Log.i("compress", "jpg start");
byte[] bytes = BitmapUtil.compressBitmapToBytes(file.getPath(), 600, 0, 60, Bitmap.CompressFormat.JPEG);
File jpg = new File(path, imgName + "compress.jpg");
FileUtils.writeByteArrayToFile(jpg, bytes);
Log.i("compress", "jpg finish");
Log.i("compress", "----------------------------------------------------");
Log.i("compress", "webp start");
byte[] bytes1 = BitmapUtil.compressBitmapToBytes(file.getPath(), 600, 0, 60, Bitmap.CompressFormat.WEBP);//分別是圖片路徑,寬度高度,質(zhì)量,和圖片類型,重點在這里。
File webp = new File(path, imgName + "compress.webp");
FileUtils.writeByteArrayToFile(webp, bytes1);
Log.i("compress", "webp finish");
} catch (IOException e) {
e.printStackTrace();
}
}
我的測試機器也是Oneplus 1 ,CM13,所以需要獲取相應(yīng)的權(quán)限。
利用RxJava來做線程操作,在io線程里做了耗時操作。
public static byte[] compressBitmapToBytes(String filePath, int reqWidth, int reqHeight, int quality, Bitmap.CompressFormat format) {
Bitmap bitmap = getSmallBitmap(filePath, reqWidth, reqHeight);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(format, quality, baos);
byte[] bytes = baos.toByteArray();
bitmap.recycle();
Log.i(TAG, "Bitmap compressed success, size: " + bytes.length);
return bytes;
}
public static Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(filePath, options);
options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
options.inJustDecodeBounds = false;
// options.inPreferQualityOverSpeed = true;
return BitmapFactory.decodeFile(filePath, options);
}
public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
int h = options.outHeight;
int w = options.outWidth;
int inSampleSize = 0;
if (h > reqHeight || w > reqWidth) {
float ratioW = (float) w / reqWidth;
float ratioH = (float) h / reqHeight;
inSampleSize = (int) Math.min(ratioH, ratioW);
}
inSampleSize = Math.max(1, inSampleSize);
return inSampleSize;
}
根據(jù)輸入的寬高值計算分辨率的縮小比例,再根據(jù)輸入的壓縮質(zhì)量數(shù)值,壓縮圖片,獲得壓縮后bitmap,然后再將其保存成本地文件。
這是非常常見的圖片壓縮手段。
4. WebP對比
我用我日常生活里拍下的照片來做簡單的對比測試,不是特別嚴謹,僅供簡單的參考。
拍照設(shè)備是刷了CM13的 一加 1 。拍照場景都是日常生活特別常見的。
以下是原圖預(yù)覽,就不一個個放出來了,太大。

縮小分辨率,同時壓縮質(zhì)量
| 文件名 | 照片原圖 | 壓縮后jpg | 壓縮后webp | 壓縮比 |
|---|---|---|---|---|
| 1.jpg | 5760 kb | 98 kb | 74 kb | 24.49% |
| 2.jpg | 4534 kb | 64 kb | 35 kb | 45.31% |
| 3.jpg | 4751 kb | 93 kb | 68 kb | 26.88% |
| 4.jpg | 7002 kb | 121 kb | 95 kb | 21.49% |
| 5.jpg | 5493 kb | 111 kb | 91 kb | 18.02% |
平均壓縮比是:27.24%
按照原圖大小,不縮小分辨率,僅壓縮質(zhì)量。
| 文件名 | 照片原圖 | 壓縮后jpg | 壓縮后webp | 壓縮比 |
|---|---|---|---|---|
| 3.jpg | 4751 kb | 796 kb | 426 kb | 46.48% |
至此,我們就非常方便的使用了webp來對圖片進行更加極致的壓縮,兼顧了圖片體積和質(zhì)量。
呃,csdn不支持上傳webp的圖片。你們可以看壓縮包。
我嫌麻煩,可能不會傳壓縮包了……所以,你們看截圖吧~
睜大眼睛對比一下有啥區(qū)別,不縮小分辨率,僅壓縮質(zhì)量,這個3.jpg可是有46.48%的壓縮比噢。
這個場景是晚上在燈光充足的室內(nèi)吃飯拍的。


5. 用Gzip再壓縮
剛剛是針對本地圖片的壓縮,接下來,我們需要將圖片傳輸?shù)椒?wù)器。這個過程依然有優(yōu)化空間,就是利用Gzip。
Gzip的作用對象是整個請求體,具體來說是對請求體中的內(nèi)容進行可逆的壓縮,類似pc上zip的那種。
Gzip壓縮的請求體,需要加入相應(yīng)的header: 「Content-Encoding:gzip」。
這事情Retrofit會幫你做好。
后臺服務(wù)器接收到在此類型的請求,就會對請求體解壓,因此需要后端的支持。
另外要注意的是,Gzip針對比較大的請求體壓縮效果不錯,尤其是未經(jīng)過壓縮的純文本類型。
如果請求本來就很小,那么就不要使用gzip壓縮了,壓縮包自己的元數(shù)據(jù)可能比你的請求體還大,得不償失。你可以自己測試一下,我估計zip和gzip的壓縮字典比較類似,可以直接在pc上做測試。
6. Retrofit對請求Gzip壓縮
網(wǎng)絡(luò)請求方面,我項目里使用Retrofit (OKHttp) + RxJava。
Retrofit的Gzip壓縮,本質(zhì)上是通過OKHttp的攔截器來完成的。
0攔截請求
1加入header
2壓縮請求
3發(fā)送出去
搞定,方便。
https://github.com/square/okhttp/wiki/Interceptors
/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
@Override public Response intercept(Interceptor.Chain chain) throws IOException {
Request originalRequest = chain.request();
if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
return chain.proceed(originalRequest);
}
Request compressedRequest = originalRequest.newBuilder()
.header("Content-Encoding", "gzip")
.method(originalRequest.method(), gzip(originalRequest.body()))
.build();
return chain.proceed(compressedRequest);
}
private RequestBody gzip(final RequestBody body) {
return new RequestBody() {
@Override public MediaType contentType() {
return body.contentType();
}
@Override public long contentLength() {
return -1; // We don't know the compressed length in advance!
}
@Override public void writeTo(BufferedSink sink) throws IOException {
BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
body.writeTo(gzipSink);
gzipSink.close();
}
};
}
}
7. 請求體抓包對比
我會用Fiddler4 監(jiān)測整個請求過程,以方便我們得知實際傳輸了多大的數(shù)據(jù)。
上傳的具體代碼就不發(fā)了,這個不是重點。
我把請求抓包之后,把request這個保存下來。

這是同時上傳兩張圖片的大小
| 文件名 | 請求體大小 |
|---|---|
| WebP+Gzip | 169kb |
| WebP | 222kb |
用Gzip壓縮 比不加Gzip 又小 23% ! jpg我就不發(fā)了,可以按照前面的估算一下~
WebP比Jpg小27%,然后gzip+webp又比單純的webp小23%,節(jié)省下來的流量多可觀?。?/span>
8. 最后
WebP默認只支持Android 4.0以上,現(xiàn)在項目最低支持的版本是16,所以沒什么問題。如果你的項目最低要支持到2.0,好像也有第三方支持,但是我建議你抓產(chǎn)品出去打一頓。
在我們的項目里,iOS沒用使用該技術(shù)。
根據(jù)下面的參考鏈接的數(shù)據(jù)以及我本人測試數(shù)據(jù)來看,WebP不僅大大的節(jié)省了用戶的流量,同時還可以加速圖片傳輸速度。就照片傳輸?shù)慕嵌葋砜矗欠浅S欣摹?/p>
如果還是有人告訴你:“IOS做不了,不做了?!保昂笈_XXX不做了?!?/p>
我建議你抓產(chǎn)品出去打一頓。
相關(guān)參考:
http://isux.tencent.com/introduction-of-webp.html(產(chǎn)品經(jīng)理看這個)
http://blog.csdn.net/GeekLei/article/details/41147479 (后臺需要看這個)
https://developers.google.com/speed/webp/
http://www.infoq.com/cn/articles/sdk-optimazation?utm_campaign=infoq_content&utm_source=infoq&utm_medium=feed&utm_term (他們表示IOS也沒有用上)
http://blog.csdn.net/mingchunhu/article/details/8155742(Android全部都要看)
相關(guān)文章
Android?Activity?View加載與繪制流程深入刨析源碼
這篇文章主要介紹了Android?Activity?View的加載與繪制流程源碼分析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08
Android應(yīng)用開發(fā)中數(shù)據(jù)的保存方式總結(jié)
這篇文章主要介紹了Android應(yīng)用開發(fā)中數(shù)據(jù)的保存方式總結(jié),包括對ROM、SD卡、SharedPreference這三種方式實現(xiàn)的核心代碼的精選,需要的朋友可以參考下2016-02-02
Android編程之截屏實現(xiàn)方法(包括scrollview與listview)
這篇文章主要介紹了Android編程之截屏實現(xiàn)方法,包括截取scrollview與listview屏幕的相關(guān)技巧,以及截屏圖片的生成與保存技巧,需要的朋友可以參考下2015-11-11
android apk反編譯到j(luò)ava源碼的實現(xiàn)方法
Android由于其代碼是放在dalvik虛擬機上的托管代碼,所以能夠很容易的將其反編譯為我們可以識別的代碼,本文將詳細介紹,需要的朋友可以參考下2012-12-12

