最近项目准备尝试使用webp来缩小包的体积,因而抽空对相关知识进行了调研和学习。javascript
至于什么是webp,使用webp有什么好处我就不赘述了,具体能够参考腾讯isux上的这篇文章WebP 探寻之路,大体了解下就ok了。html
入手大体须要考虑如下几个问题:java
下面就跟着上面3个问题开始进行。android
这个官方提供了相互转化的工具,以及具体的使用方式,能够参考: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动画文件的。
cwebp weixin.png -o weixin.webp复制代码
dwebp weixin.webp -o weixin.png复制代码
###(3) gif 转化为webp
./gif2webp xingye.gif -o xingye.webp复制代码
每一个命令都有一堆options,能够本身研究下
Webp在app中通常能够用于两个方面
###(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+)复制代码
那么结合文档和实验,大体能够有以下的结论:
看到这个结论,那么就是你们的产品最低的支持版本了。
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个选择:
第一种,目前来看没什么好介绍的,换图便可。
主要看第二种的处理了,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如下的版本是不须要上述操做的。
固然是根据后缀,那么咱们如今能获取的仅仅是图片的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,欢迎踩坑与交流。