Android 端 WebP 图片压缩与传输的一点探索

澳门新葡亰网站注册 7

1. 简介

直到4g时代,流量依然是宝贵的东西。而移动网络传输中,最占流量的一种载体:图片,成为了我们移动开发者不得不关注的一个问题。

我们关注的问题,无非是图片体积和质量如何达到一个比较和谐的平衡,希望得到质量不错的图片同时体积还不能太大。

走在时代前列的谷歌给出了一个不错的答案——WebP。

WebP是一种图片文件格式,在相同的压缩指标下,webp的有损压缩能比jpg小
25-34%。而在我自己的测试里,有时候能小50%。

作者:学军
又拍云 CDN 服务公测 WebP
自适应功能,为客户减少图片资源大小。本文我们将一起来阐述WebP兼容的来龙去脉。

2. 大企业背书

WebP在2010年发布第一个版本,到现在已经6年了,谷歌旗下的各种网站G+、以及非常有代表性的YouTube,他的视频文件格式WebM就是基于WebP构造的。

据说腾讯、淘宝、美团也有部分应用。

前言
我们知道,理想的网页应该在 1
秒内打开,而在页面的整体大小中,图片往往是所占比例最大的一部分(大约占到
60%
以上,更多了解请点击),也可以参照如下图所示。优化图片不仅可以加快页面显示,还能降低移动网络的流量费用。原图产生的
PNG、JPEG、GIF 和 SVG
图片一般都有很大的压缩余地。下文将重点介绍一款图片新格式:WebP,从而揭开它神秘的面纱。

3. Android 端 JPG 转换 WebP

RxJava线程转换:

    String[] imgs = new String[]{"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"};
    String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/Pictures/test/";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
//      test = Api.getBuilder().create(Test.class);
        String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE
                , Manifest.permission.READ_PHONE_STATE
                , Manifest.permission.CAMERA};
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            requestPermissions(permissions, 0);
        }
        compress();
    }
    private void compress() {
        Observable.from(imgs)
                .subscribeOn(Schedulers.io())
                .doOnNext(new Action1<String>() {
                    @Override
                    public void call(String imgName) {
                        compress(imgName);
                    }
                })
                .subscribe();
    }
    private void compress(String imgName) {
        try {
            File file = new File(path, imgName);
            Log.i("compress", "jpg start");
            byte[] bytes = BitmapUtil.compressBitmapToBytes(file.getPath(), 600, 0, 60, Bitmap.CompressFormat.JPEG);
            File jpg = new File(path, imgName + "compress.jpg");
            FileUtils.writeByteArrayToFile(jpg, bytes);
            Log.i("compress", "jpg finish");
            Log.i("compress", "----------------------------------------------------");
            Log.i("compress", "webp start");
            byte[] bytes1 = BitmapUtil.compressBitmapToBytes(file.getPath(), 600, 0, 60, Bitmap.CompressFormat.WEBP);//分别是图片路径,宽度高度,质量,和图片类型,重点在这里。
            File webp = new File(path, imgName + "compress.webp");
            FileUtils.writeByteArrayToFile(webp, bytes1);
            Log.i("compress", "webp finish");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

我的测试机器也是Oneplus 1 ,CM13,所以需要获取相应的权限。

利用RxJava来做线程操作,在io线程里做了耗时操作。

public static byte[] compressBitmapToBytes(String filePath, int reqWidth, int reqHeight, int quality, Bitmap.CompressFormat format) {
        Bitmap bitmap = getSmallBitmap(filePath, reqWidth, reqHeight);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        bitmap.compress(format, quality, baos);
        byte[] bytes = baos.toByteArray();
        bitmap.recycle();
        Log.i(TAG, "Bitmap compressed success, size: " + bytes.length);
        return bytes;
    }

    public static Bitmap getSmallBitmap(String filePath, int reqWidth, int reqHeight) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inJustDecodeBounds = false;
//      options.inPreferQualityOverSpeed = true;
        return BitmapFactory.decodeFile(filePath, options);
    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {
        int h = options.outHeight;
        int w = options.outWidth;
        int inSampleSize = 0;
        if (h > reqHeight || w > reqWidth) {
            float ratioW = (float) w / reqWidth;
            float ratioH = (float) h / reqHeight;
            inSampleSize = (int) Math.min(ratioH, ratioW);
        }
        inSampleSize = Math.max(1, inSampleSize);
        return inSampleSize;
    }

根据输入的宽高值计算分辨率的缩小比例,再根据输入的压缩质量数值,压缩图片,获得压缩后bitmap,然后再将其保存成本地文件。
这是非常常见的图片压缩手段。

澳门新葡亰网站注册 1

4. WebP对比

我用我日常生活里拍下的照片来做简单的对比测试,不是特别严谨,仅供简单的参考。

拍照设备是刷了CM13的 一加 1 。拍照场景都是日常生活特别常见的。

以下是原图预览,就不一个个放出来了,太大。

澳门新葡亰网站注册 2

解决方案:使用 WebP 优化图像
1、什么是 WebP ?
澳门新葡亰网站注册,WebP(发音
weppy),是一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式
VP8。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45%
的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之后,WebP 还是可以减少
28% 的文件大小。更多了解参照 WebP
官网文档。

缩小分辨率,同时压缩质量

文件名 照片原图 压缩后jpg 压缩后webp 压缩比
1.jpg 5760 kb 98 kb 74 kb 24.49%
2.jpg 4534 kb 64 kb 35 kb 45.31%
3.jpg 4751 kb 93 kb 68 kb 26.88%
4.jpg 7002 kb 121 kb 95 kb 21.49%
5.jpg 5493 kb 111 kb 91 kb 18.02%

平均压缩比是:27.24%

按照原图大小,不缩小分辨率,仅压缩质量。

文件名 照片原图 压缩后jpg 压缩后webp 压缩比
3.jpg 4751 kb 796 kb 426 kb 46.48%

至此,我们就非常方便的使用了webp来对图片进行更加极致的压缩,兼顾了图片体积和质量。

呃,csdn不支持上传webp的图片。你们可以看压缩包。
我嫌麻烦,可能不会传压缩包了……所以,你们看截图吧~

睁大眼睛对比一下有啥区别,不缩小分辨率,仅压缩质量,这个3.jpg可是有46.48%的压缩比噢。
这个场景是晚上在灯光充足的室内吃饭拍的。

澳门新葡亰网站注册 3
澳门新葡亰网站注册 4

2、WebP 客户端兼容性
Google Chrome 和 Opera 浏览器以及许多其他工具和软件库都支持
WebP,但是当前并非所有浏览器都支持 WebP,支持的情况参见如下图所示:

5. 用Gzip再压缩

刚刚是针对本地图片的压缩,接下来,我们需要将图片传输到服务器。这个过程依然有优化空间,就是利用Gzip。

Gzip的作用对象是整个请求体,具体来说是对请求体中的内容进行可逆的压缩,类似pc上zip的那种。

Gzip压缩的请求体,需要加入相应的header: 「Content-Encoding:gzip」。
这事情Retrofit会帮你做好。

后台服务器接收到在此类型的请求,就会对请求体解压,因此需要后端的支持。

另外要注意的是,Gzip针对比较大的请求体压缩效果不错,尤其是未经过压缩的纯文本类型。

如果请求本来就很小,那么就不要使用gzip压缩了,压缩包自己的元数据可能比你的请求体还大,得不偿失。你可以自己测试一下,我估计zip和gzip的压缩字典比较类似,可以直接在pc上做测试。

澳门新葡亰网站注册 5

6. Retrofit对请求Gzip压缩

网络请求方面,我项目里使用Retrofit (OKHttp)

  • RxJava。

Retrofit的Gzip压缩,本质上是通过OKHttp的拦截器来完成的。

0拦截请求
1加入header
2压缩请求
3发送出去

搞定,方便。

/** This interceptor compresses the HTTP request body. Many webservers can't handle this! */
final class GzipRequestInterceptor implements Interceptor {
  @Override public Response intercept(Interceptor.Chain chain) throws IOException {
    Request originalRequest = chain.request();
    if (originalRequest.body() == null || originalRequest.header("Content-Encoding") != null) {
      return chain.proceed(originalRequest);
    }
    Request compressedRequest = originalRequest.newBuilder()
        .header("Content-Encoding", "gzip")
        .method(originalRequest.method(), gzip(originalRequest.body()))
        .build();
    return chain.proceed(compressedRequest);
  }
  private RequestBody gzip(final RequestBody body) {
    return new RequestBody() {
      @Override public MediaType contentType() {
        return body.contentType();
      }
      @Override public long contentLength() {
        return -1; // We don't know the compressed length in advance!
      }
      @Override public void writeTo(BufferedSink sink) throws IOException {
        BufferedSink gzipSink = Okio.buffer(new GzipSink(sink));
        body.writeTo(gzipSink);
        gzipSink.close();
      }
    };
  }
}

详细的支持情况参见 WebP 支持情况。

7. 请求体抓包对比

我会用Fiddler4 监测整个请求过程,以方便我们得知实际传输了多大的数据。

上传的具体代码就不发了,这个不是重点。

我把请求抓包之后,把request这个保存下来。

澳门新葡亰网站注册 6

这是同时上传两张图片的大小

文件名 请求体大小
WebP+Gzip 169kb
WebP 222kb

用Gzip压缩 比不加Gzip 又小 23% !
jpg我就不发了,可以按照前面的估算一下~

WebP比Jpg小27%,然后gzip+webp又比单纯的webp小23%,节省下来的流量多可观啊!

WebP 自适应方案解决了什么问题?使用 WebP
图片格式,显而易见,可以享受的好处就是:
1、WebP
格式的图片可以提供更好的压缩比和更小的文件大小,可以减少网络传输,使得网络传输的速度更快;
2、网络传输的流量减少了,可以节省 CDN 流量消耗,节省带宽成本;
3、当前还有一部分客户端并不支持 WebP 格式,可以通过 CDN
层去判断,对于支持的客户端,响应 WebP
格式的图片;不支持的客户端,响应原图。从而实现无缝适配。

8. 最后

WebP默认只支持Android
4.0以上,现在项目最低支持的版本是16,所以没什么问题。如果你的项目最低要支持到2.0,好像也有第三方支持,但是我建议你抓产品出去打一顿。

在我们的项目里,iOS没用使用该技术。

根据下面的参考链接的数据以及我本人测试数据来看,WebP不仅大大的节省了用户的流量,同时还可以加速图片传输速度。就照片传输的角度来看,是非常有利的。

如果还是有人告诉你:“IOS做不了,不做了。”,“后台XXX不做了。”

我建议你抓产品出去打一顿。

相关参考:

(产品经理看这个)

 (后台需要看这个)

 (他们表示IOS也没有用上)

(Android全部都要看)

如何判断浏览器支持 WebP 格式?
如下图所示,是通过 Chrome
浏览器开发者工具抓包显示,可以通过查看响应头和请求头相关字段,得知该图片已经被
CDN 自动转化为 WebP 格式:
事实上,可以通过 HTTP Accept 头就可以判断该浏览器是否支持 WebP
格式的图片,结合上图,Chrome 浏览器在发起请求的时候,会带上 Accept
头,且其值包括:image/webp,此时就说明该浏览器支持 WebP
格式图片;参见如下格式:accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,/;q=0.8
另外,通过 HTTP 响应头里面的 Content-Type
就可以判断文件的真正类型,例如:Content-Type:image/webp,就说明此次服务端响应的是
WebP 格式的副本图片。

如何通过又拍云 CDN 实现 WebP 自适应?
如下图所示,在 CDN 层做 WebP 的自适应,需要考虑如下几个方面:

澳门新葡亰网站注册 7

1、CDN 如何判断客户端是否支持 WebP ?
该部分目前是通过 HTTP Accept 头来判断的,如果支持,则返回 WebP
副本并进行缓存;如果不支持,则返回原图。详细介绍参照上文介绍。
2、CDN 如何实现实时图片格式转换?
针对用户源站并非 WebP 格式图片的时候,CDN
层需要支持将原图图片的实时转换为 WebP 格式副本,这个在 CDN
层面是无缝支持的。这样的场景是这样的:
客户端浏览器请求一个图片资源,例如:;

CDN 通过 Accept 头已经判断客户端浏览器支持 WebP 格式的图片;

CDN 回用户源站取回原图并将原图实时转为 WebP
格式的图片,并响应给客户端浏览器。

这里值得强调的是,又拍云 CDN
已经无缝兼容了各种作图场景和访问方式,包括:
原图访问 ,示例:/a.jpg