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的互转

这个官方提供了相互转化的工具,以及具体的使用方式,可以参考:

截个图,可以看到左侧的功能列表,包含一系列的功能,encode、decode、view等…

因为有比较详细的文档,这里简单介绍下:

首先下载工具:

我这里下载的是对应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动画文件的。

(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支持的源码了,点击下载:

如果你的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判断这种方式应该会更加精确,具体可以查找下相关代码。

对了,如果你的基类是FragmentActivity,那就不需要去设置什么LayoutFactory了,直接复写其onCreateView方法:

onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    final View view = super.onCreateView(parent, name, 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 ,欢迎入群

微信公众号:hongyangAndroid

(欢迎关注,不要错过每一篇干货,支持投稿)

参考

时间: 2024-10-14 08:38:39

Android Webp 完全解析 快来缩小apk的大小吧的相关文章

缩小apk的大小

原文地址:https://developer.android.com/topic/performance/reduce-apk-size.html 减小APK的体积 This lesson teaches you to Understand the APK Structure Reduce Resource Count and Size Reduce Native and Java Code Maintain Multiple Lean APKs You should also read Shr

Unity编译Android的原理解析和apk打包分析

作者介绍:张坤 最近由于想在Scene的脚本组件中,调用Android的Activity的相关接口,就需要弄明白Scene和Activity的实际对应关系,并对Unity调用Android的部分原理进行了研究. 本文主要探讨Scene和Activity之间的关系,以及Unity打包apk和Android studio打包apk的差别在什么地方?找到这种差别之后,可以怎么运用起来? 本文需要用到的工具: Android反编译工具--apktool Android studio自带的反编译功能 一.

android 利用 aapt 解析 apk 的应用名称 包名 版本号 权限等信息

在上传各大市场时发现 apk 上传后能自动解析出应用名称.包名.版本号.使用权限等信息,所以就研究了一下 1 直接解压 apk 解析  AndroidManifest.xml 是不行的,因为 apk 打包时 AndroidManifest.xml 文件被压缩混淆了. 2 主要有两种实现方式 1) 通过 aapt (android asset packaging tool) aapt 是打包.更新资源的一个工具,可以解析出 apk 的资源信息 2) apktool 著名的反编译工具,先反编译出 A

Android Service完全解析,关于服务你所需知道的一切(下) (转载)

转自:http://blog.csdn.net/guolin_blog/article/details/9797169 转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在 上一篇文章中,我们学习了Android Service相关的许多重要内容,包括Service的基本用法.Service和Activity进行通信.Service的销毁方式. Service与Thread的关系.以及如何创建前台Service.以上

Android Service完全解析,关于服务你所需知道的一切(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/11952435 相信大多数朋友对Service这个名词都不会陌生,没错,一个老练的Android程序员如果连Service都没听说过的话,那确实也太逊了.Service作为Android四大组件之一,在每一个应用程序中都扮演着非常重要的角色.它主要用于在后台处理一些耗时的逻辑,或者去执行某些需要长期运行的任务.必要的时候我们甚至可以在程序退出的情况下,让Service在后台继续保持

Android第五期 - 更新自己的apk本地与网络两种方法

首先是本地: ParseXmlService部分: package com.szy.update; import java.io.InputStream; import java.util.HashMap; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.w3c.dom.Document; import org.w3c.dom.Element

Android ActionBar完全解析,使用官方推荐的最佳导航栏(下)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/25466665 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文. http://developer.android.com/guide/topics/ui/actionbar.html 限于篇幅的原因,在上篇文章中我们只学习了ActionBar基础部分的知识,那么本篇文章我们将接着上一章的内容继续学习,探究一下ActionBar

Android ActionBar完全解析,使用官方推荐的最佳导航栏(上)

转载请注明出处:http://blog.csdn.net/guolin_blog/article/details/18234477 本篇文章主要内容来自于Android Doc,我翻译之后又做了些加工,英文好的朋友也可以直接去读原文. http://developer.android.com/guide/topics/ui/actionbar.html Action Bar是一种新増的导航栏功能,在Android 3.0之后加入到系统的API当中,它标识了用户当前操作界面的位置,并提供了额外的用

Android Service完全解析,关于服务你所需知道的一切(下)

转载请注册出处:http://blog.csdn.net/guolin_blog/article/details/9797169 在上一篇文章中,我们学习了Android Service相关的许多重要内容,包括Service的基本用法.Service和Activity进行通信.Service的销毁方式.Service与Thread的关系.以及如何创建前台Service.以上所提到的这些知识点,基本上涵盖了大部分日常开发工作当中可能使用到的Service技术.不过关于Service其实还有一个更加