美文网首页大前端-BFEiOS精品文章
app图片优化-webp格式图片原理及在Android、IOS中

app图片优化-webp格式图片原理及在Android、IOS中

作者: 天空的守望者 | 来源:发表于2017-11-20 17:27 被阅读1229次

    参考:http://www.jianshu.com/p/ff8444fbc773

    http://www.jianshu.com/p/555859783f63

    http://blog.csdn.net/lmj623565791/article/details/53240600

    http://www.jianshu.com/p/ed7562a34af1

    http://www.jianshu.com/p/0244e431fb3c

    一、app体积优化的几种方式

    1.使用TinyPng来优化png格式图片大小,就是使用方法压缩png图片。

    2.不包含透明像素的图片,改为JPEG格式,这样可以减少图片的大小。

    3.大图拆分为小图,大小也会减少。

    4.使用九宫格图片,也就是.9.png的图片,android和ios都适用,对于比较大的图片和使用范围,可是形成无损拉伸效果。

    5.使用IconFont,通过字体文件来构建纯色图,不占图片体积,通过加载字库文件,来显示纯色图片。阿里的字库IOS接入第三发库:github上的开源库IconFont和阿里的FontAwesomeKit(这个是个很重要的趋势,以后将特意写篇文章来说IconFont)

    6.本章将讲解的webp格式图片。谷歌2011年发布WebP,一直对它做了很多升级和改变,2014年WebP开始支持动图。webp格式是图片压缩更加极致,比常用的jpg png格式小24%-35%,webp格式已经支持大部分浏览器,Android 原生。

    二、webp格式的原理

    2.1 来源

    WebP文件格式来源于VP8视频编解码(你可能更知道WebM)。VP8编解码器的其中一个强大特性是帧内预测压缩,或者说,视频的每一帧都被压缩,后续帧与帧之间的差异也会被压缩。这就是WebP的由来:WebM文件里单个被压缩的帧。更精确的说WebP的核心来则WebM。

    2.1 原理分析 —— 有损模式

    WebP的有损模式是建立在与一张静止的JPG竞争的基础上,因此,你会注意到在对文件处理上有一些惊人相识。

    *宏块(MacroBlocking)

    编码器的第一个阶段是将图片分割成不同"宏块"。典型的宏块包括一个16x16的亮度像素块,和两个8x8的色度像素块。这个阶段非常像JPEG格式里转换颜色空间,对色度通道降低采样,以及细分图片。


    image

    *预测

    宏块里每个4x4的子块都有一个预测模型。(又名过滤)。在PNG里过滤用得非常多,它对每一行都做同样的事,而WebP过滤的是每一块。它是这样处理的,在一个块周围定义两组像素:有一行在它上面为A,在它左边那一列为L。


    image

    利用A和L,编码器会将它们放在一个4x4的测试像素块填满,并确定哪一个生成了最接近原始块的值。这些用不同方法填满的块叫做"预测块"。

    Horiz prediction(水平预测)——将块的每列使用左列(L)数据的副本进行填充。

    Vertical Prediction(垂直预测)——将块的每行使用上列(A)数据的副本进行填充。

    DC Prediction(DC 预测)——将块使用 A 上列的像素与 L 左列的像素的平均值进行填充。

    True Motion (TrueMotion 预测)——一种超级先进的模式,我暂时不讲。

    值得注意的是,4x4的亮度块还有另外6种模式,但你现在只需知道这些就好;)

    image

    基本流程是我们找到这个快最佳的预测块,并导出过滤结果(剩余误差),然后送到下个阶段。

    *JPGify it

    WebP编码的最后阶段看起来非常像我们的老朋友JPG:

    对块里剩余的值执行DCT过滤

    DCT矩阵后量化

    转成量化矩阵后重新排序,然后送到一个静态压缩器里。


    image

    这有主要有两点不同:

    在DCT阶段输入的数据不是原始的数据块本身,而是预测后的数据

    WebP用得静态压缩器是算术压缩器,它和JPG用的霍夫曼编码器类似。

    从最后的结果看WebP感觉有点像加强版的JPG。WebP的预测阶段相比JPG是最大的优势,它减少了特殊颜色,使得在以后的处理阶段能更有效的压缩图片数据。

    WebP只是比JPG所有处理过程多了一个预测模式,在数据压缩方面就把JPG干倒。

    三、webp在Android中支持的情况

    Webp在app中的使用一般包括两个方面:

    1.对与服务端交互过程中使用webp图片

    从Android 4.0 开始就对webp图片进行支持,直接使用就可以了。但是要注意:4.2.1+对webp格式的decode、encode是完全支持的(包含半透明的webp图);对于4.0+ 到 4.2.1 ,只支持完全不透明的decode、encode的webp图;4.0 以下,应该是默认不支持webp了。

    Android 4.0之前的兼容可以通过官方的源码库(需要手动转为so文件),也可以使用前人封装好的库:webp-android-backport和webp-android。

    示例代码:

    
    InputStreamis= getAssets().open("weixin.webp");
    
    Bitmapbitmap=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);privatestaticbyte[]streamToBytes(InputStreamis) {
    
    ByteArrayOutputStream os =newByteArrayOutputStream(1024);byte[] buffer =newbyte[1024];intlen;try{while((len =is.read(buffer)) >=0) {
    
    os.write(buffer,0, len);
    
    }
    
    }catch(java.io.IOException e) {
    
    }returnos.toByteArray();
    
    }
    
    

    2.应用中的资源文件

    4.0以上简单的使用:

    直接将png转化为webp,放到res/drawable目录

    image

    3.在4.0以下支持

    
    public classMainActivityextendsAppCompatActivity {
    
    private static final int[]LL=new int[]
    
    {//
    
    android.R.attr.src,//
    
    };
    
    @Override
    
    protected voidonCreate(Bundle savedInstanceState) {
    
    if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN){
    
    LayoutInflaterCompat.setFactory(LayoutInflater.from(this),newLayoutInflaterFactory() {
    
    @Override
    
    publicView onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    
    AppCompatDelegate delegate = getDelegate();
    
    View view = delegate.createView(parent, name, context, attrs);
    
    if(viewinstanceofImageView) {
    
    ImageView imageView = (ImageView) view;
    
    TypedArray a = context.obtainStyledAttributes(attrs,LL);
    
    intwebpSourceResourceID = a.getResourceId(0,0);
    
    if(webpSourceResourceID ==0) {
    
    returnview;
    
    }
    
    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);
    
    finalBitmap webpBitmap = WebPDecoder.getInstance().decodeWebP(data);
    
    imageView.setImageBitmap(webpBitmap);
    
    }
    
    a.recycle();
    
    }
    
    returnview;
    
    }
    
    });
    
    }
    
    super.onCreate(savedInstanceState);
    
    setContentView(R.layout.activity_main);
    
    }
    
    }
    
    

    写一个基类,所有的Activity都继承此基类。大致逻辑为:对于4.2以下的版本,我们设置一个LayoutInflaterFactory,当创建ImageView的时候,因为AppCompatActivity,ImageView的创建是由上述代码中的delegate指向的对象完成的,我们通过传入attrs,取出用户声明的src属性,经过一系列操作转化为bitmap,最好设置到创建好的ImageView上。

    四、webp在IOS中支持的情况

    这里先说的一点xcode目前无法像Android Studio一样支持webp作为资源,所以IOS目前无法将webp作为资源直接在IDE中支持,只能作为网络图片和H5图片显示在IOS程序中。

    1.IOS本地中使用webp图片

    本地使用webp图片,可以使用SDWebImage里面有个webP 框架,可以将webp-->NSData-->UIImage最后变为可识别的图片格式直接给控件调用。

    但是需要额外需要‘webp’包

    $pod 'SDWebImage/WebP'


    image
    
    #ifdef SD_WEBP
    
    #import
    
    // Fix for issue #416 Undefined symbols for architecture armv7 since WebP introduction when deploying to device
    
    void WebPInitPremultiplyNEON(void);
    
    void WebPInitUpsamplersNEON(void);
    
    void VP8DspInitNEON(void);
    
    @interface UIImage (WebP)
    
    + (UIImage *)sd_imageWithWebPData:(NSData *)data;
    
    @end
    
    #endif
    
    

    2.IOS 网络或webview使用webp格式图片

    webView 等网页上如果用的是 webP 图片,iOS 直接解析的话,会显示?图片。

    思路 1:

    A. 在网页加载出后截取到HTML及内部的JS后,调用JS预先准备好的方法获取需要转码的webP格式图片下载地址(其实一个一个的遍历也行).

    B. 在App 本地开启线程下载图片,下载完成后,将图片转码由 webP—> png—>Base64(因为实验出直接用 png/jpg 的话 没用)

    C. 将 Base64及原图片下载地址一一对应调用JS准备好的方法进行替换

    D. 将下载后的图片进行缓存,并进行管理

    注意点:

    A. 图片在最终显示成功前会显示成?,此处为了用户体验应该采用占位图片

    B. 图片显示成功前应该保持网页布局不调整,需要由 JS 预先设置好布局

    C. 图片在本地的缓存需要管理

    
    //在 `webView` 加载完 `HTML` 后,解析源码,执行相应的 `JS` 方法
    
    -(void)webViewDidFinishLoad:(UIWebView *)webView
    
    {
    
    //获取`HTML`代码
    
    NSString *lJs = @"document.documentElement.innerHTML";
    
    NSString *str = [webView stringByEvaluatingJavaScriptFromString:lJs];
    
    //执行约定好的方法,获取需要下载的 webp 图片
    
    NSString *imgs = [self.webView stringByEvaluatingJavaScriptFromString:@"YongChe.getAllWebPImg();"];
    
    NSArray *array = [NSJSONSerialization JSONObjectWithData:[imgs dataUsingEncoding:NSUTF8StringEncoding] options:0 error:nil];
    
    //此处,做示范,只转换第一个,将图片下载下来,并且转为 PNG 后,再转成 Base64,传给 JS 脚本执行
    
    NSString *imgUrl = array.firstObject;
    
    __weak typeof (self) weakSelf = self;
    
    [SDWebImageCustomeDownLoad downloadWithURL:[NSURL URLWithString:imgUrl] progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
    
    NSString *base = [NSString stringWithFormat:@"data:image/png;base64,%@",imgBase];
    
    NSString *js = [NSString stringWithFormat:@"YongChe.replaceWebPImg('%@','%@')",imageURL,base];
    
    [weakSelf.webView stringByEvaluatingJavaScriptFromString:js];
    
    }];
    
    }
    
    
    
    + (void)downloadWithURL:(NSURL *)url progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock
    
    {
    
    //    [self sd_cancelCurrentImageLoad];
    
    //    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    if (url) {
    
    //        __weak __typeof(self)wself = self;
    
    id operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:0 progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
    
    //            if (!wself) return;
    
    dispatch_main_sync_safe(^{
    
    //                if (!wself) return;
    
    if (image && completedBlock)
    
    {
    
    completedBlock(image, error, cacheType, url);
    
    return;
    
    }
    
    else if (image) {  //没有回调,但是图片下载完成了
    
    } else {    //image 下载失败
    
    }
    
    if (completedBlock && finished) {
    
    completedBlock(image, error, cacheType, url);
    
    }
    
    });
    
    }];
    
    //这一步,将这个 View 之前的下载操作全部取消,然后将这次的操作放进去
    
    //        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    
    } else {
    
    dispatch_main_async_safe(^{
    
    NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];
    
    if (completedBlock) {
    
    completedBlock(nil, error, SDImageCacheTypeNone, url);
    
    }
    
    });
    
    }
    
    }
    
    

    思路2:

    采用webP的目的就是减少网络传输数据量,加快请求整体速度,现在的问题就在于webView不识别webP这种格式而已,再想想 iOS内的所有网络请求/响应我们都可以控制,那我们获取到数据时,转化成它识别的给它就可以了,而可以帮我们在拿到数据时进行某些操作,操作完以后又正常进行网络流程的。这就需要用到NSURLProtocol。

    NSURLProtocol是 iOS里面的URL Loading System的一部分,但是从它的名字来看,你绝对不会想到它会是一个对象,可是它偏偏是个对象。。。而且还是抽象对象(可是OC里面没有抽象这一说)。平常我们做网络相关的东西基本很少碰它,但是它的功能却强大得要死。

    *可以拦截UIWebView,基于系统的NSUIConnection或者NSUISession进行封装的网络请求。

    *忽略网络请求,直接返回自定义的Response

    *修改request(请求地址,认证信息等等)

    *返回数据拦截

    *干你想干的。。。

    对URL Loading System不清楚的,可以看看下面这张图,看看里面有哪些类:


    image

    思路很简单,就是拦截请求URL带.png.jpg.gif的请求,首先去缓存里面取,有的话直接返回,没有的去请求,并保存本地。

    
    static NSString * const hasInitKey = @"JYCustomWebViewProtocolKey";
    
    @interface JYCustomWebViewProtocol ()
    
    @property (nonatomic, strong) NSMutableData *responseData;
    
    @property (nonatomic, strong) NSURLConnection *connection;
    
    @end
    
    @implementation JYCustomWebViewProtocol
    
    + (BOOL)canInitWithRequest:(NSURLRequest *)request
    
    {
    
    if ([request.URL.scheme isEqualToString:@"http"]) {
    
    NSString *str = request.URL.path;
    
    //只处理http请求的图片
    
    if (([str hasSuffix:@".png"] || [str hasSuffix:@".jpg"] || [str hasSuffix:@".gif"])
    
    && ![NSURLProtocol propertyForKey:hasInitKey inRequest:request]) {
    
    return YES;
    
    }
    
    }
    
    return NO;
    
    }
    
    + (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
    
    NSMutableURLRequest *mutableReqeust = [request mutableCopy];
    
    //这边可用干你想干的事情。。更改地址,提取里面的请求内容,或者设置里面的请求头。。
    
    return mutableReqeust;
    
    }
    
    - (void)startLoading
    
    {
    
    NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
    
    //做下标记,防止递归调用
    
    [NSURLProtocol setProperty:@YES forKey:hasInitKey inRequest:mutableReqeust];
    
    //查看本地是否已经缓存了图片
    
    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
    
    NSData *data = [[SDImageCache sharedImageCache] diskImageDataBySearchingAllPathsForKey:key];
    
    if (data) {
    
    NSURLResponse *response = [[NSURLResponse alloc] initWithURL:mutableReqeust.URL
    
    MIMEType:[NSData sd_contentTypeForImageData:data]
    
    expectedContentLength:data.length
    
    textEncodingName:nil];
    
    [self.client URLProtocol:self
    
    didReceiveResponse:response
    
    cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    
    [self.client URLProtocol:self didLoadData:data];
    
    [self.client URLProtocolDidFinishLoading:self];
    
    }
    
    else {
    
    self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
    
    }
    
    }
    
    - (void)stopLoading
    
    {
    
    [self.connection cancel];
    
    }
    
    #pragma mark- NSURLConnectionDelegate
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
    
    [self.client URLProtocol:self didFailWithError:error];
    
    }
    
    #pragma mark - NSURLConnectionDataDelegate
    
    - (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
    
    {
    
    self.responseData = [[NSMutableData alloc] init];
    
    [self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
    
    [self.responseData appendData:data];
    
    [self.client URLProtocol:self didLoadData:data];
    
    }
    
    - (void)connectionDidFinishLoading:(NSURLConnection *)connection
    
    {
    
    UIImage *cacheImage = [UIImage sd_imageWithData:self.responseData];
    
    //利用SDWebImage提供的缓存进行保存图片
    
    [[SDImageCache sharedImageCache] storeImage:cacheImage
    
    recalculateFromImage:NO
    
    imageData:self.responseData
    
    forKey:[[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL]
    
    toDisk:YES];
    
    [self.client URLProtocolDidFinishLoading:self];
    
    }
    
    

    注意点:

    每次只能只有一个protocol进行处理,如果有多个自定义protocol,系统将采取你registerClass的倒序进行调用,一旦你需要对这个请求进行处理,那么接下来的所有相关操作都需要这个protocol进行管理。

    一定要注意标记请求,不然你会无限的循环下去。。。因为一旦你需要处理这个请求,那么系统会创建你这个protocol的实例,然后你自己又开启了connection进行请求的话,又会触发URL Loading system的回调。系统给我们提供了+ (void)setProperty:(id)value forKey:(NSString *)key inRequest:(NSMutableURLRequest *)request;和+ (id)propertyForKey:(NSString *)key inRequest:(NSURLRequest *)request;这两个方法进行标记和区分。

    五、总结

    工具:jpeg/png可以转换成为webp格式,官方转换工具:webp转换工具

    在网页端已经大规模使用webp格式进行传输、显示了,但是在app端还有很多人在用png和jpg,webp是一个很好的可以减小app大小的图片格式,在Android 端4.0之后才支持作为资源存在,在ios端无法作为资源存在,但是无论Android 和IOS端在webview 和网络途径上有方式来实现webp格式解析,对于网络大图需求多的app,webp格式是很好的,建议所有Android app换webp格式,IOS app看需求使用网络显示webp格式。

    相关文章

      网友评论

        本文标题:app图片优化-webp格式图片原理及在Android、IOS中

        本文链接:https://www.haomeiwen.com/subject/qsnvvxtx.html