您好,欢迎来到源码搜藏!分享精神,快乐你我!提示:担心找不到本站?在百度搜索“源码搜藏”,网址永远不丢失!
  • 首 页
  • 在线工具
  • 当前位置:首页 > 安卓源码 > 技术博客 >

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

    时间:2016-11-28 10:26 来源:互联网 作者:源码搜藏 浏览:收藏 挑错 推荐 打印

    一、概述 最近项目准备尝试使用webp来缩小包的体积,于是抽空对相关知识进行了调研和学习。 至于什么是webp,使用webp有什么好处我就不赘述了,具体可以参考腾讯isux上的这篇文章WebP 探寻之路,大致了解下就ok了。 入手大致需要考虑以下几个问题: 如何将现

    一、概述

    最近项目准备尝试使用webp来缩小包的体积,于是抽空对相关知识进行了调研和学习。

    至于什么是webp,使用webp有什么好处我就不赘述了,具体可以参考腾讯isux上的这篇文章WebP 探寻之路,大致了解下就ok了。

    入手大致需要考虑以下几个问题:

    • 如何将现有的jpeg/png等图转化为webp?
    • webp格式的图片如何使用?
    • 有没有兼容性的问题?

    下面就跟着上面3个问题开始进行。

    二、jpeg/png到webp的互转

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

    • https://developers.google.com/speed/webp/docs/cwebp

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

    截个图,可以看到左侧的功能列表,包含一系列的功能,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
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    大致有4个命令工具,分别用于png等转换为webp;webp转化为png;git转化为webp;查看webp图片;最后一个是用于创建webp动画文件的。

    (1) jpeg、png 转为webp [cwebp]

    cwebp weixin.png -o weixin.webp
    • 1

    (2) webp转为jpeg、png [dwebp]

    dwebp weixin.webp -o weixin.png
    • 1

    (3) gif 转化为webp

    ./gif2webp xingye.gif -o xingye.webp
    • 1

    每个命令都有一堆options,可以自己研究下

    三、使用

    Webp在app中一般可以用于两个方面

    • 一个是对与服务端交互过程中使用webp图片
    • 另一个是应用中的资源文件

    (1)与服务端交互使用webp图片

    这种方式非常简单,因为从Android4.0开始已经对webp图片进行的支持。

    下面我们写个例子,这里我准备了一个webp的图片,我直接放到assets目录,然后编写如下代码:

    # 这是一个完全不透明图的测试
    Bitmap bitmap = BitmapFactory.decodeStream(getAssets().open("icon.webp"));
    imageView.setImageBitmap(bitmap);
    
    • 1
    • 2
    • 3
    • 4

    找了台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+)
    • 1
    • 2
    • 3

    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
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    然后将其WebDecoder的辅助类copy到项目中即可,注意保持原有包名。

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

    ok,然后就可以用它提供的decode的方法了:

    WebPDecoder.getInstance().decodeWebP(byte[] encoded)
    • 1

    于是,上述以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();
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    ok,这样就可以对4.2.1以下的webp图片进行decode了。

    服务端下发的图片为webp格式,然后app去decode显示即可。

    注:webp-android这个库只提供了decode方法,如果需要encode需要自己去添加;建议有时间,看下源码中提供的方法,自己利用源码结合ndk相关知识自己做so文件的生成.

    (3)应用中的资源文件

    除了上述去加载外部图片的方式以外,还有个使用场景就是将项目中的资源文件直接替换为webp。

    简单的使用:

    直接将png转化为webp,放到res/drawable目录,我们看看效果

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

    这样就可以了~~

    从目前来看有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" />
    • 1
    • 2
    • 3
    • 4

    这样就可以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);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    一般我们的项目中的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
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    当然应该也可以通过图片的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;
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    ok,到此应该对于webp都有了一定的认识,也应该大致了解了在Android使用webp的兼容性的问题,以及如何处理。

    文章中还有很多细节的地方没有去处理,后面要踩得坑还有很多,后续还会有一篇博客来写踩到的坑。

    如果你也想用webp,欢迎踩坑与交流。

    快来缩小apk的大小吧 Android Webp完全解析转载http://www.codesocang.com/anzhuoyuanma/boke/33914.html
    标签:网站源码