摘要:一了解,是一种同时提供了有损压缩与无损压缩的图片文件格式,是新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。这里建议所有基于的流量优化都最好用的判断包住,避免带来问题。
首先,这是一个基于具体业务的组件优化方案,我尽量把业务逻辑从代码中抽离出来,部分地方代码可能有删减。
现在这个方案是用于一个多图片的新闻类应用,粗略估计过,用户在浏览完第一页所有新闻(共48篇),会消耗流量达100M,其中98M为图片,这里值得优化的空间非常大。
针对这种情况,我们先后使用过的优化包含:wifi条件下预载所有文章、图片和js、css数据;重用所有已经下载的js、css和图片的缓存;后台图片的压缩。
后台压缩和WebP化依赖第三方多媒体处理服务器,已知比较好的国内服务有腾讯优图和七牛。这里我们采用的七牛的服务。
我们的后台通过七牛的图片压缩(包含质量和分辨率),我们将首页流量由100m减少到了80m,依然有极大的提升空间。因此客户端采用基于WebP的流量压缩方案,将流量由80m压缩到了20m,减少了75%!相对于最初的处理,流量减少了80%!(android大多数机型支持WebP animated,压缩能达到80%,但iOS的解码对于WebP animated图片支持并不好,经常会出现失败的情况,所以iOS最终压缩率取决于首页中gif图的个数和大小,实际测试结果,优化幅度大概60%-80%之间)
在准备做这项优化之前,查阅过很多资料,发现WebP适配的相关文章博客,都只是介绍简单的功能性适配,所以,并没有得到什么好的思路。
于是,在三周的时间里,我一直边测试边优化,在没有初步方案的情况下,一点点完成功能,最终整理代码,解耦组件,整理出一套效果非常理想,并且使用方便的解决方案。
一、了解 WebPWebP,是一种同时提供了有损压缩与无损压缩的图片文件格式,是Google新推出的影像技术,它可让网页图档有效进行压缩,同时又不影响图片格式兼容与实际清晰度,进而让整体网页下载速度加快。
WebP 无损压缩的图片可以比同样大小的 PNG 小 26%;
WebP 有损压缩的图片可以比同样大小的 JPEG 小 25-34%;
WebP 支持无损的透明图层通道,代价只需增加 22% 的字节存储空间;
WebP 有损透明图像可以比同样大小的 PNG 图像小3倍。
WebP在Native支持方面上,早已比较成熟,据说淘宝客户端在两年前就使用了WebP(主要是Native使用),后来H5全面使用,WebView的WebP采用插件的方式支持。
在安卓上,WebP的支持是非常简单的,毕竟都是谷歌的东西,自己当然要支持,但是在iOS的WebKit内核(UIWebView和WKWebView)上,是不能直接支持的。不过最近传言macOS 10.12上的Safari有测试WebP的迹象,暂时还不太明朗。
二、准备工作由于OS X不支持原生WebP解码,所以,可以先安装一个工具。推荐使用Homebrew,具体使用参考 http://brew.sh/index_zh-cn.html
安装完成后,使用命令
$brew install webp
就可以安装libwebp了。
客户端方面,Native图片加载使用的SDWebImage,该组件直接支持WebP的解码。需要在将预编译宏’WebP’置为1,并在pod中引入’iOS-WebP’即可。
服务端方面,我们采用七牛图片服务器,默认传给客户端的参数是一张jpg或者png的图片链接,通过修改url的请求参数实现对WebP图片的获取。相关规则可以参考七牛开发文档。
三、具体方案实现首先考虑,请求的webp图片是通过url参数拼接完成的,所以,需要对客户端内请求的所有图片URL做处理,必须全部命中。而且,将来的缓存也应基于此URL进行处理,所以,添加一个NSURL分类,URL的处理由这个分类统一处理,所有的URL替换最终都会指向这个分类中的方法,耦合度基本可以将至最低。
@interface NSURL (ReplaceWebP) - (NSURL *)qd_replaceToWebPURLWithScreenWidth; - (NSString *)qd_defultWebPURLCacheKey; - (BOOL)qd_isShouldReplaceImageFormat; @end
下面是替换URL和缓存key的核心处理代码
static NSString * const qdHost = @"img.host.com"; @implementation NSURL (ReplaceWebP) - (NSString *)qd_defultWebPURLCacheKey { if (![self qd_isShouldReplaceImageFormat]) { return self.absoluteString; } NSString *key; if ([self isWebPURL]) { key = self.absoluteString; } else { key = [self qd_replaceToWebPURLWithScreenWidth].absoluteString; } return key; } - (NSURL *)qd_replaceToWebPURLWithImageWidth:(int)width { if ([self qd_isShouldReplaceImageFormat]) { NSString *urlStr; if ([self URLStringcontainFomartString:@"?"]) { if ([self URLStringcontainFomartString:@"format/jpg"]) { urlStr = [self.absoluteString stringByReplacingOccurrencesOfString:@"format/jpg" withString:@"format/webp"]; } else { NSString *suffixStr = @"imageView2/0/format/webp/ignore-error/1"; urlStr = [NSString stringWithFormat:@"%@/%@", self.absoluteString, suffixStr]; } } else { NSString *pathExtension = [[self.absoluteString.pathExtension componentsSeparatedByString:@"-"] firstObject]; urlStr = [NSString stringWithFormat:@"%@.%@-WebPiOSW%d",self.absoluteString.stringByDeletingPathExtension, pathExtension, width]; } return [NSURL URLWithString:urlStr]; } return self; } - (NSURL *)qd_replaceToWebPURLWithScreenWidth { int width = (int)([UIScreen mainScreen].bounds.size.width * [UIScreen mainScreen].scale); return [self qd_replaceToWebPURLWithImageWidth:(int)width]; }
所有的URL替换,最终都会到 - (NSURL *)qd_replaceToWebPURLWithImageWidth:(int)width 这个方法中来
下面是条件过滤,确保100%命中所有需要替换的图片格式
- (BOOL)isQDHost { NSString *nsModel = [UIDevice currentDevice].model; BOOL s_isiPad = [nsModel hasPrefix:@"iPad"]; if (s_isiPad) return NO; return [self URLStringcontainFomartString:qdHost]; } - (BOOL)qd_isShouldReplaceImageFormat { if (![self isQDHost]) { return NO; } if ([self isWebPURL]) { return NO; } NSArray *extensions = @[@".jpg", @".jpeg", @".png"]; for (NSString *extension in extensions) { if ([self.absoluteString.lowercaseString rangeOfString:extension options:NSCaseInsensitiveSearch].location != NSNotFound){ return YES; } } return NO; } - (BOOL)URLStringcontainFomartString:(NSString *)string { return ([self.absoluteString.lowercaseString rangeOfString:string options:NSCaseInsensitiveSearch].location != NSNotFound); } - (BOOL)isWebPURL { return [self URLStringcontainFomartString:@"-webp"] || [self URLStringcontainFomartString:@"/webp"]; } @end
所以,替换URL这个功能,被完全抽离出来,之后的代码,只需要考虑具体逻辑的问题了。
2. Native 图片请求替换
Native图片加载使用的SDWebImage,首先需要理解SD的代码,确定是最终的图片下载是调用的哪个方法
- (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
所有的图片下载,最终都走到了这个方法中,所以,替换URL应该在这个方法的最前面实现。
{ if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } if (![url isKindOfClass:NSURL.class]) { url = nil; } url = [url qd_replaceToWebPURLWithScreenWidth]; ... ... }
由于在评估了难度之后,我们果断地把SDWebImage从Pods中移除,手动添加一个子工程,这样可以比较方便地修改内部实现,而不至于用swizzling这种黑魔法来修改传入参数。这个技能虽然炫酷,然而很多情况下,杀敌一万,自损两万,不建议经常使用。
因修改了url值,若在上层通过SDImageCache判断是否有本地缓存时,也需要对url先做qd_defultWebPURLCacheKey来获取其真实缓存的key。这一部分比较简单。
3. WebView 图片请求替换
这一部分是这个方案的难度所在。
webkit内核现在都不支持解析WebP格式的图片,这里主要采用的iOS系统的NSURLProtocol来替换其网络请求(不了解NSURLProtocol,可以动动自己勤劳的小手Google一下),再将网络回包数据进行转码成jpg或者png(为了透明度),再返回给webview进行渲染的。
友情链接,NSURLProtocol用法,大神文章
同样的,iOS在此处依然不对gif进行任何处理。
另外,NSURLProtocol会拦截全局的网络流量,为避免误伤,这里需要多带带识别是否是WebView发起的请求,可以通过识别request中的UA是否包含”AppleWebKit”来实现。
@implementation QDWebURLProtocol + (BOOL)canInitWithRequest:(NSURLRequest *)request { NSString *ua = [request valueForHTTPHeaderField:@"User-Agent"]; if ([request.URL qd_isShouldReplaceImageFormat] && [ua lf_containsSubString:@"AppleWebKit"]) { return YES; } }
这里可以接管所有WebView中需要替换的图片URL。
下面,会自动调用startLoading方法,这里采用了一个非常特别的方式处理
- (void)startLoading { if ([self.request.URL qd_isShouldReplaceImageFormat]) { [[SDWebImageManager sharedManager] downloadImageWithURL:self.request.URL options:0 progress:nil completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { NSData *data; if ([imageURL.absoluteString.lowercaseString lf_containsSubString:@".png"]) { data = UIImagePNGRepresentation(image); } else { data = UIImageJPEGRepresentation(image, 1); } [self.client URLProtocol:self didLoadData:data]; [self.client URLProtocolDidFinishLoading:self]; }]; return; } self.connection = [NSURLConnection connectionWithRequest:self.request delegate:self]; }
是不是很奇特,由SDWebImageManager直接接管图片请求,手动finishLoading。
首先需要明确,WebP节约流量,究竟是怎么样的原理:
所谓图片格式,是采用何种解码编码方式决定的,所有数据最终一定是变成二进制数据,NSData;
既然UIWebView不支持解码WebP,我们可以让图片在网络中以WebP格式的NSData传递,本地收到data后,解码成UIWebView可以识别的UIImage;事实上,Native方面就是这么做就可以达到目标了,然而在WebView的请求中,无论我们本地做了何种处理,最终交给WebView的也一定是NSData,所以,需要再把UIImage编码成jpg或者png(之所以我们没有把gif也转WebP,就是因为从WebP的动图UIImage,转码成NSData这条路走不通,于是我们放弃了gif转WebP)。
所以,大致的数据路径如下:
本地发送WebP请求 ---> Server ---> 返回WebP格式Data ---> Data经谷歌的WebP decode得到UIImage ---> 将UIImage对象编码成JPG或PNG格式NSData ---> 替换本应交给WebView的WebP格式Data ---> WebView接收JPG或PNG格式Data ---> 渲染图片
在最开始,这里并不是这么写的,当时是在系统的
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
方法中转码处理。按这个思路写,代码越写越散,BUG也越来越多。所以,换了个思路,既然SD可以支持WebP,为什么不用他来全面托管呢?
这样的话,原生请求和WebView的图片缓存也可以经由SD统一起来,所以,这应该是一个好的方案。
这样的话,WebP的所有请求都已经可以处理(wifi预加载暂时不管,因为是自己写的downloader,替换URL后直接改把缓存指向修改就可以),之后要处理缓存的问题
4. 图片缓存处理
以前的代码已经实现了内部文章的缓存,包含js、css以及image等。这里通过NSURLCache来实现。相应的,基于WebP的图片缓存的读取也应该在NSURLCache中处理,在先处理完URL后,用新的Key来进行映射。
这里建议所有基于WebView的流量优化都最好用UA的判断包住,避免带来问题。因为无论NSURLProtocol还是NSURLCache都是全局网络控制。
篇幅略长,具体缓存处理放在下一篇介绍。
文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。
转载请注明本文地址:https://www.ucloud.cn/yun/61792.html
摘要:开启验证上传一张新图片,使用手安卓版本访问已支持域名的图片,如果请求带了,检查返回图片格式是否为如果旧的图片未按预期返回,返回了或原图可能是结点缓存,正常天后过期回源则会返回图片。 对于图片较多的网站,本文结合具体案例给出了如何基于CDN的sharpP自适应图片无痛接入方案,据统计效果可在原图基础上节省60%-75%的流量。作者:陈忱 出处:腾云阁文章 目前移动端运营素材大部分依赖图...
摘要:在客户端基于图片格式的流量优化上这篇文章中,已经介绍了格式图片的下载使用,仅仅只有这样还远远不够,还需要对已经下载的图片数据进行缓存。二图片缓存关于的缓存,系统提供了一个类,。而且,既然是全局影响,肯定要用包起来,防止误伤其他缓存。 在iOS 客户端基于 WebP 图片格式的流量优化(上)这篇文章中,已经介绍了WebP格式图片的下载使用,仅仅只有这样还远远不够,还需要对已经下载的图片数...
摘要:的支持程度实际上比你想的可能要好得多。的安卓浏览器从版本起开始官方支持最初发布于年月,版本起开始部分支持。安卓版从起开始支持。而且目前并无添加支持的任何打算。浏览器市场份额截至年月的数据显示,占有约的市场份额,以约位居第二。 本文转载自:众成翻译译者:文蔺链接:http://www.zcfy.cc/article/862原文:https://optimus.keycdn.com/sup...
阅读 1525·2023-04-26 01:36
阅读 2695·2021-10-08 10:05
阅读 2732·2021-08-05 09:57
阅读 1515·2019-08-30 15:52
阅读 1172·2019-08-30 14:12
阅读 1239·2019-08-30 11:17
阅读 3075·2019-08-29 13:07
阅读 2394·2019-08-29 12:35