蹉跎莫遣韶光老,人生唯有读书好。这篇文章主要讲述Android Webp 完全解析 快来缩小apk的大小吧相关的知识,希望能为你提供帮助。
本文在我的微信公众号: 鸿洋( hongyangandroid) 首发。一、概述 最近项目准备尝试使用webp来缩小包的体积, 于是抽空对相关知识进行了调研和学习。
转载请标明出处:
http://blog.csdn.net/lmj623565791/article/details/53240600;
本文出自:【张鸿洋的博客】
至于什么是webp, 使用webp有什么好处我就不赘述了, 具体可以参考腾讯isux上的这篇文章WebP 探寻之路, 大致了解下就ok了。
入手大致需要考虑以下几个问题:
- 如何将现有的jpeg/png等图转化为webp?
- webp格式的图片如何使用?
- 有没有兼容性的问题?
二、jpeg/png到webp的互转 这个官方提供了相互转化的工具, 以及具体的使用方式, 可以参考:
- https://developers.google.com/speed/webp/docs/cwebp
文章图片
截个图, 可以看到左侧的功能列表, 包含一系列的功能, encode、decode、view等…
因为有比较详细的文档, 这里简单介绍下:
首先下载工具:
- webp download page
下载完成后解压, 然后进入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图片
- 另一个是应用中的资源文件
这种方式非常简单, 因为从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
当然了, 你也可以使用前人已经封装好的库:
- webp-android-backport
- webp-android
首先下载下来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个选择:
- 仅替换不存在局部透明的图片, 如果项目最小版本是4.0, 可以不引入so直接使用。
- 全部替换( 需要引入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的图片资源呢?
让人开心的是, 可以拿到的。
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
( 欢迎关注, 不要错过每一篇干货, 支持投稿)
文章图片
- 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
推荐阅读
- Android自定义Transition动画
- Android Studio使用时源码到处报红色警告,运行时又没错
- android studio学习之一
- Android第一课——HelloWorld
- Mosquitto搭建Android推送服务Mosquitto简介及搭建
- Android内部自带的SQLite数据库操作dos命令
- Android - ADB 的使用
- Android需求之点击跳转至市场评价
- 动手试试Android Studio插件开发