Android Webp 徹底解析 快來縮小apk的大小吧

1、概述

最近項目準備嘗試使用webp來縮小包的體積,因而抽空對相關知識進行了調研和學習。javascript

至於什麼是webp,使用webp有什麼好處我就不贅述了,具體能夠參考騰訊isux上的這篇文章WebP 探尋之路,大體瞭解下就ok了。html

入手大體須要考慮如下幾個問題:java

  • 如何將現有的jpeg/png等圖轉化爲webp?
  • webp格式的圖片如何使用?
  • 有沒有兼容性的問題?

下面就跟着上面3個問題開始進行。android

2、jpeg/png到webp的互轉

這個官方提供了相互轉化的工具,以及具體的使用方式,能夠參考:git

截個圖,能夠看到左側的功能列表,包含一系列的功能,encode、decode、view等...github

由於有比較詳細的文檔,這裏簡單介紹下:web

首先下載工具:api

我這裏下載的是對應mac os的libwebp-0.4.1-mac-10.8-2.tar.gz bash

下載完成後解壓,而後進入bin目錄:架構

MacBook-Pro:bin zhanghongyang01$ pwd
/Users/zhanghongyang01/hongyang/works/libwebp-0.4.1-mac-10.8 2/bin
MacBook-Pro:bin zhanghongyang01$ ls -l
total 5152
-rwxr-xr-x@ 1 zhanghongyang01  staff  1302772  9 20  2014 cwebp
-rwxr-xr-x@ 1 zhanghongyang01  staff   421508  9 20  2014 dwebp
-rwxr-xr-x@ 1 zhanghongyang01  staff   402128  9 20  2014 gif2webp
-rwxr-xr-x@ 1 zhanghongyang01  staff   264588  9 20  2014 vwebp
-rwxr-xr-x@ 1 zhanghongyang01  staff   237376  9 20  2014 webpmux複製代碼

大體有4個命令工具,分別用於png等轉換爲webp;webp轉化爲png;git轉化爲webp;查看webp圖片;最後一個是用於建立webp動畫文件的。

(1) jpeg、png 轉爲webp [cwebp]

cwebp weixin.png -o weixin.webp複製代碼

(2) webp轉爲jpeg、png [dwebp]

dwebp weixin.webp -o weixin.png複製代碼

###(3) gif 轉化爲webp

./gif2webp xingye.gif -o xingye.webp複製代碼

每一個命令都有一堆options,能夠本身研究下

3、使用

Webp在app中通常能夠用於兩個方面

  • 一個是對與服務端交互過程當中使用webp圖片
  • 另外一個是應用中的資源文件

###(1)與服務端交互使用webp圖片

這種方式很是簡單,由於從Android4.0開始已經對webp圖片進行的支持。

下面咱們寫個例子,這裏我準備了一個webp的圖片,我直接放到assets目錄,而後編寫以下代碼:

# 這是一個徹底不透明圖的測試
Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open("icon.webp"));
imageView.setImageBitmap(bitmap);複製代碼

找了臺4.0.4(API15)的三星手機(ps:實在是找不到4.0的手機了),運行感受還不錯喲~

正在竊喜的時候,我又換了張圖片,由於有些時候咱們的圖部分區域是透明瞭,因而我找了張圖片,轉化爲webp,按照上述的代碼,一樣的操做,運行完成後,發現,整個圖都顯示不出來了

趕忙找了個4.2.2(API17)的手機,顯示正常。

因而看一眼文檔:

文檔上對webp decode和encode的支持,是這樣寫的:

decode / encode
(Android 4.0+)
(Lossless, Transparency, Android 4.2.1+)複製代碼

developer.android.com/guide/appen…

那麼結合文檔和實驗,大體能夠有以下的結論:

  • 4.2.1+ 對於webp的decode、encode是徹底支持的(包含半透明的webp圖)
  • 對於4.0+ 到 4.2.1 ,只支持徹底不透明的decode、encode的webp圖
  • 4.0 如下,應該是默認不支持webp了

看到這個結論,那麼就是你們的產品最低的支持版本了。

4.2.1起步的話,目前來看,我是不能接受的,因此只有引入so來作低版本兼容了。

###(2)兼容so的獲取

好在官方已經提供了相關webp支持的源碼了,點擊下載:

若是你的ndk的知識足夠的話,能夠本身利用源碼,去生成so文件使用。

固然了,你也可使用前人已經封裝好的庫:

咱們這裏選擇使用第二個庫,這裏選擇copy它生成的so文件以及輔助類到項目中,你也能夠根據其readme打包一個aar出來使用。

首先下載下來webp-android,而後切換到webp-android/src/main/jni,執行ndk-build

而後等待執行結束,能夠在其/webp-android/src/main/libs目錄下copy出你須要的so,若是須要其餘cpu架構的so,能夠本身修改Application.mk文件。

/webp-android/src/main/libs
.
├── armeabi
│   └── libwebp_evme.so
├── armeabi-v7a
│   └── libwebp_evme.so
└── x86
    └── libwebp_evme.so複製代碼

而後將其WebDecoder的輔助類copy到項目中便可,注意保持原有包名。

ok,而後就能夠用它提供的decode的方法了:

WebPDecoder.getInstance().decodeWebP(byte[] encoded)複製代碼

因而,上述以InputStream爲webp圖片源的代碼能夠改寫爲:

# 大體的示例代碼
InputStream is = getAssets().open("weixin.webp");
Bitmap bitmap = null;
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.JELLY_BEAN) {
    bitmap = WebPDecoder.getInstance().decodeWebP(streamToBytes(is));
} else {
    bitmap = BitmapFactory.decodeStream(is);
}
imageView.setImageBitmap(bitmap);


private static byte[] streamToBytes(InputStream is) {
    ByteArrayOutputStream os = new ByteArrayOutputStream(1024);
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = is.read(buffer)) >= 0) {
            os.write(buffer, 0, len);
        }
    } catch (java.io.IOException e) {
    }
    return os.toByteArray();
}複製代碼

ok,這樣就能夠對4.2.1如下的webp圖片進行decode了。

服務端下發的圖片爲webp格式,而後app去decode顯示便可。

注:webp-android這個庫只提供了decode方法,若是須要encode須要本身去添加;建議有時間,看下源碼中提供的方法,本身利用源碼結合ndk相關知識本身作so文件的生成.

###(3)應用中的資源文件

除了上述去加載外部圖片的方式之外,還有個使用場景就是將項目中的資源文件直接替換爲webp。

簡單的使用:

直接將png轉化爲webp,放到res/drawable目錄,咱們看看效果

這樣就能夠了~~

從目前來看有2個選擇:

  1. 僅替換不存在局部透明的圖片,若是項目最小版本是4.0,能夠不引入so直接使用。
  2. 所有替換(須要引入so的支持)

第一種,目前來看沒什麼好介紹的,換圖便可。

主要看第二種的處理了,webp-android提供了一種作法是這樣的:

<me.everything.webp.WebPImageView android:layout_width="wrap_content" android:layout_height="wrap_content" webp:webp_src="@drawable/your_webp_image" />複製代碼

這樣就能夠happy的使用webp了。

可是我一點都不happy,使用webp不少都是已經存在的項目,讓我去使用自定義類還要加屬性,多麻煩,萬一發現坑,我還得一個一個換回去,堅定不幹。

因此咱們須要一種,能夠無縫切換的方式,基本不費力也能還原。

最無縫的方式,就是不動本來的佈局文件了,那麼如何去動態修改ImageView使其支持Webp呢(4.-)?

其實咱們的SDK也有相似的作法,好比對不少View支持了tint屬性,本來是不支持的,突然就支持了,怎麼作到的呢?

就是在根據佈局文件中ImageView標籤名稱,建立的時候去作了一些手腳,若是你一臉懵逼,能夠先看Android 探究 LayoutInflater setFactory

實際上就是利用LayoutInflaterFactory了,有了方案,那麼代碼就好寫了:

public class MainActivity extends AppCompatActivity {

    private static final int[] LL = new int[]
            { //
                    android.R.attr.src,//
            };

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
            LayoutInflaterCompat.setFactory(LayoutInflater.from(this), new LayoutInflaterFactory() {
                @Override
                public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {

                    AppCompatDelegate delegate = getDelegate();
                    View view = delegate.createView(parent, name, context, attrs);

                    if (view instanceof ImageView) {
                        ImageView imageView = (ImageView) view;
                        TypedArray a = context.obtainStyledAttributes(attrs, LL);
                        int webpSourceResourceID = a.getResourceId(0, 0);
                        if (webpSourceResourceID == 0) { 
                            return view;
                          }
                        InputStream rawImageStream = getResources().openRawResource(webpSourceResourceID);
                        byte[] data = streamToBytes(rawImageStream);
                        final Bitmap webpBitmap = WebPDecoder.getInstance().decodeWebP(data);
                        imageView.setImageBitmap(webpBitmap);
                        a.recycle();
                    }
                    return view;
                }
            });
        }
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}複製代碼

通常咱們的項目中的Activity都存在一個基類,那麼直接在其中添加上述代碼便可。

大體邏輯爲:對於4.2如下的版本,咱們設置一個LayoutInflaterFactory,當建立ImageView的時候,由於AppCompatActivity,ImageView的建立是由上述代碼中的delegate指向的對象完成的,咱們經過傳入attrs,取出用戶聲明的src屬性,通過一系列操做轉化爲bitmap,最好設置到建立好的ImageView上。

這樣,剩下的咱們直接將圖換成webp就行了,若是發現不適合,只須要去掉這個factory設置的代碼便可。

正在我竊喜的時候,突然發現了一個問題。

就是假設個人資源文件更換並不完全,還存在部分png的圖,可是png的圖在4.2如下的版本是不須要上述操做的。

  • 那麼問題來了,如何區分webp和非webp的圖片資源呢?

固然是根據後綴,那麼咱們如今能獲取的僅僅是圖片的resId,還能拿到文件完整的名稱嗎?

讓人開心的是,能夠拿到的。

TypedValue value = new TypedValue();

getResources().getValue(webpSourceResourceID, value, true);
String resname = value.string.toString().substring(13, 
        value.string.toString().length());
if (resname.endsWith(".webp")) {
    // do
}複製代碼

固然應該也能夠經過圖片的header信息來判斷,header判斷這種方式應該會更加精確,具體能夠查找下相關代碼。

ok,到此應該對於webp都有了必定的認識,也應該大體瞭解了在Android使用webp的兼容性的問題,以及如何處理。

文章中還有不少細節的地方沒有去處理,後面要踩得坑還有不少,後續還會有一篇博客來寫踩到的坑。

若是你也想用webp,歡迎踩坑與交流。



歡迎關注個人公衆號:
搜索公衆號:鴻洋或者hongyangAndroid


參考

相關文章
相關標籤/搜索