編輯:關於Android編程
最近項目准備嘗試使用webp來縮小包的體積,於是抽空對相關知識進行了調研和學習。
至於什麼是webp,使用webp有什麼好處我就不贅述了,具體可以參考騰訊isux上的這篇文章WebP 探尋之路,大致了解下就ok了。
入手大致需要考慮以下幾個問題:
下面就跟著上面3個問題開始進行。
這個官方提供了相互轉化的工具,以及具體的使用方式,可以參考:
https://developers.google.com/speed/webp/docs/cwebp截個圖,可以看到左側的功能列表,包含一系列的功能,encode、decode、view等…
因為有比較詳細的文檔,這裡簡單介紹下:
首先下載工具:
webp download page我這裡下載的是對應mac os的libwebp-0.4.1-mac-10.8-2.tar.gz
下載完成後解壓,然後進入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動畫文件的。
cwebp weixin.png -o weixin.webp
dwebp weixin.webp -o weixin.png
./gif2webp xingye.gif -o xingye.webp
每個命令都有一堆options,可以自己研究下
Webp在app中一般可以用於兩個方面
看到這個結論,那麼就是大家的產品最低的支持版本了。
4.2.1起步的話,目前來看,我是不能接受的,所以只有引入so來做低版本兼容了。
好在官方已經提供了相關webp支持的源碼了,點擊下載:
libwebp-0.5.1.tar.gz如果你的ndk的知識足夠的話,可以自己利用源碼,去生成so文件使用。
當然了,你也可以使用前人已經封裝好的庫:
webp-android-backport我們這裡選擇使用第二個庫,這裡選擇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文件的生成.
除了上述去加載外部圖片的方式以外,還有個使用場景就是將項目中的資源文件直接替換為webp。
簡單的使用:
直接將png轉化為webp,放到res/drawable目錄,我們看看效果
這樣就可以了~~
從目前來看有2個選擇:
第一種,目前來看沒什麼好介紹的,換圖即可。
主要看第二種的處理了,webp-android提供了一種做法是這樣的:
這樣就可以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判斷這種方式應該會更加精確,具體可以查找下相關代碼。
對了,如果你的基類是FragmentActivity,那就不需要去設置什麼LayoutFactory了,直接復寫其onCreateView方法:
onCreateView(View parent, String name, Context context, AttributeSet attrs) { final View view = super.onCreateView(parent, name, context, attrs); if(view == null){ if (name.equals("ImageView")) { view = new ImageView(context,attrs); } } if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { 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; } TypedValue value = new TypedValue(); getResources().getValue(webpSourceResourceID, value, true); String resname = value.string.toString().substring(13, value.string.toString().length()); if (resname.endsWith(".webp")) { InputStream rawImageStream = getResources().openRawResource(webpSourceResourceID); byte[] data = streamToBytes(rawImageStream); Bitmap webpBitmap = WebPDecoder.getInstance().decodeWebP(data); imageView.setImageBitmap(webpBitmap); } a.recycle(); } } return view; }
ok,到此應該對於webp都有了一定的認識,也應該大致了解了在Android使用webp的兼容性的問題,以及如何處理。文章中還有很多細節的地方沒有去處理,後面要踩得坑還有很多,後續還會有一篇博客來寫踩到的坑。
如果你也想用webp,歡迎踩坑與交流。
有時候我們添加的一些資源,如圖片和一些沒用的代碼,以及在添加第三方庫的時候我們只需要使用其中的一部分功能和一部分資源,那麼這個時候如果靠我們手工去怕是非常難做的,尤其是
Android Toolbar:ToolBar是Android 5.0(API Level 21)之後用來取代ActionBar的ToolBar的優勢:Toolbar本身
大家都玩微信,避免不了都多多少少會有幾個微信群,尤其是人數較多的群如果只看頭像肯定分不清楚誰是誰,只要我們在微信裡設置一下就可以將群裡面的好友名字顯示出來,
為何產生同時適配手機和平板、UI和邏輯的共享。介紹Fragment也會被加入回退棧中。Fragment擁有自己的生命周期和接受、處理用戶的事件可以動態的添加、替換和移除某