Android Webp 完全解析 快来缩小apk的大小吧

蹉跎莫遣韶光老,人生唯有读书好。这篇文章主要讲述Android Webp 完全解析 快来缩小apk的大小吧相关的知识,希望能为你提供帮助。

本文在我的微信公众号: 鸿洋( hongyangandroid) 首发。
转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/53240600;
本文出自:【张鸿洋的博客】
一、概述 最近项目准备尝试使用webp来缩小包的体积, 于是抽空对相关知识进行了调研和学习。
至于什么是webp, 使用webp有什么好处我就不赘述了, 具体可以参考腾讯isux上的这篇文章WebP 探寻之路, 大致了解下就ok了。
入手大致需要考虑以下几个问题:
  • 如何将现有的jpeg/png等图转化为webp?
  • webp格式的图片如何使用?
  • 有没有兼容性的问题?
下面就跟着上面3个问题开始进行。
二、jpeg/png到webp的互转 这个官方提供了相互转化的工具, 以及具体的使用方式, 可以参考:
  • https://developers.google.com/speed/webp/docs/cwebp
Android Webp 完全解析 快来缩小apk的大小吧

文章图片

截个图, 可以看到左侧的功能列表, 包含一系列的功能, 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 zhanghongyang01staff13027729 202014 cwebp -rwxr-xr-x@ 1 zhanghongyang01staff4215089 202014 dwebp -rwxr-xr-x@ 1 zhanghongyang01staff4021289 202014 gif2webp -rwxr-xr-x@ 1 zhanghongyang01staff2645889 202014 vwebp -rwxr-xr-x@ 1 zhanghongyang01staff2373769 202014 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, 可以自己研究下
三、使用 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+ )

https://developer.android.com/guide/appendix/media-formats.html
那么结合文档和实验, 大致可以有如下的结论:
  • 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支持的源码了, 点击下载:
  • libwebp-0.5.1.tar.gz
如果你的ndk的知识足够的话, 可以自己利用源码, 去生成so文件使用。
当然了, 你也可以使用前人已经封装好的库:
  • webp-android-backport
  • webp-android
我们这里选择使用第二个库, 这里选择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到项目中即可, 注意保持原有包名。
Android Webp 完全解析 快来缩小apk的大小吧

文章图片

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目录, 我们看看效果
Android Webp 完全解析 快来缩小apk的大小吧

文章图片

这样就可以了~~
从目前来看有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判断这种方式应该会更加精确, 具体可以查找下相关代码。
对了, 如果你的基类是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, 欢迎踩坑与交流。
欢迎关注我的微博:
http://weibo.com/u/3165018720
群号: 497438697 , 欢迎入群
【Android Webp 完全解析 快来缩小apk的大小吧】微信公众号: hongyangAndroid
( 欢迎关注, 不要错过每一篇干货, 支持投稿)
Android Webp 完全解析 快来缩小apk的大小吧

文章图片

参考
  • https://storage.googleapis.com/downloads.webmproject.org/releases/webp/index.html
  • https://developers.google.com/speed/webp/docs/api
  • https://groups.google.com/a/webmproject.org/forum/#!forum/webp-discuss
  • http://isux.tencent.com/introduction-of-webp.html
  • http://stackoverflow.com/questions/9403321/android-how-to-retrieve-file-name-and-extension-of-a-resource-by-resource-id/22063704#22063704
  • https://developer.android.com/guide/appendix/media-formats.html
  • https://github.com/alexey-pelykh/webp-android-backport
  • http://hahack.com/wiki/sundries-webp.html#android-开发中使用-webp

    推荐阅读