摘要:一了解,是一種同時(shí)提供了有損壓縮與無(wú)損壓縮的圖片文件格式,是新推出的影像技術(shù),它可讓網(wǎng)頁(yè)圖檔有效進(jìn)行壓縮,同時(shí)又不影響圖片格式兼容與實(shí)際清晰度,進(jìn)而讓整體網(wǎng)頁(yè)下載速度加快。這里建議所有基于的流量?jī)?yōu)化都最好用的判斷包住,避免帶來(lái)問(wèn)題。
首先,這是一個(gè)基于具體業(yè)務(wù)的組件優(yōu)化方案,我盡量把業(yè)務(wù)邏輯從代碼中抽離出來(lái),部分地方代碼可能有刪減。
現(xiàn)在這個(gè)方案是用于一個(gè)多圖片的新聞?lì)悜?yīng)用,粗略估計(jì)過(guò),用戶在瀏覽完第一頁(yè)所有新聞(共48篇),會(huì)消耗流量達(dá)100M,其中98M為圖片,這里值得優(yōu)化的空間非常大。
針對(duì)這種情況,我們先后使用過(guò)的優(yōu)化包含:wifi條件下預(yù)載所有文章、圖片和js、css數(shù)據(jù);重用所有已經(jīng)下載的js、css和圖片的緩存;后臺(tái)圖片的壓縮。
后臺(tái)壓縮和WebP化依賴(lài)第三方多媒體處理服務(wù)器,已知比較好的國(guó)內(nèi)服務(wù)有騰訊優(yōu)圖和七牛。這里我們采用的七牛的服務(wù)。
我們的后臺(tái)通過(guò)七牛的圖片壓縮(包含質(zhì)量和分辨率),我們將首頁(yè)流量由100m減少到了80m,依然有極大的提升空間。因此客戶端采用基于WebP的流量壓縮方案,將流量由80m壓縮到了20m,減少了75%!相對(duì)于最初的處理,流量減少了80%?。╝ndroid大多數(shù)機(jī)型支持WebP animated,壓縮能達(dá)到80%,但iOS的解碼對(duì)于WebP animated圖片支持并不好,經(jīng)常會(huì)出現(xiàn)失敗的情況,所以iOS最終壓縮率取決于首頁(yè)中g(shù)if圖的個(gè)數(shù)和大小,實(shí)際測(cè)試結(jié)果,優(yōu)化幅度大概60%-80%之間)
在準(zhǔn)備做這項(xiàng)優(yōu)化之前,查閱過(guò)很多資料,發(fā)現(xiàn)WebP適配的相關(guān)文章博客,都只是介紹簡(jiǎn)單的功能性適配,所以,并沒(méi)有得到什么好的思路。
于是,在三周的時(shí)間里,我一直邊測(cè)試邊優(yōu)化,在沒(méi)有初步方案的情況下,一點(diǎn)點(diǎn)完成功能,最終整理代碼,解耦組件,整理出一套效果非常理想,并且使用方便的解決方案。
一、了解 WebPWebP,是一種同時(shí)提供了有損壓縮與無(wú)損壓縮的圖片文件格式,是Google新推出的影像技術(shù),它可讓網(wǎng)頁(yè)圖檔有效進(jìn)行壓縮,同時(shí)又不影響圖片格式兼容與實(shí)際清晰度,進(jìn)而讓整體網(wǎng)頁(yè)下載速度加快。
WebP 無(wú)損壓縮的圖片可以比同樣大小的 PNG 小 26%;
WebP 有損壓縮的圖片可以比同樣大小的 JPEG 小 25-34%;
WebP 支持無(wú)損的透明圖層通道,代價(jià)只需增加 22% 的字節(jié)存儲(chǔ)空間;
WebP 有損透明圖像可以比同樣大小的 PNG 圖像小3倍。
WebP在Native支持方面上,早已比較成熟,據(jù)說(shuō)淘寶客戶端在兩年前就使用了WebP(主要是Native使用),后來(lái)H5全面使用,WebView的WebP采用插件的方式支持。
在安卓上,WebP的支持是非常簡(jiǎn)單的,畢竟都是谷歌的東西,自己當(dāng)然要支持,但是在iOS的WebKit內(nèi)核(UIWebView和WKWebView)上,是不能直接支持的。不過(guò)最近傳言macOS 10.12上的Safari有測(cè)試WebP的跡象,暫時(shí)還不太明朗。
二、準(zhǔn)備工作由于OS X不支持原生WebP解碼,所以,可以先安裝一個(gè)工具。推薦使用Homebrew,具體使用參考 http://brew.sh/index_zh-cn.html
安裝完成后,使用命令
$brew install webp
就可以安裝libwebp了。
客戶端方面,Native圖片加載使用的SDWebImage,該組件直接支持WebP的解碼。需要在將預(yù)編譯宏’WebP’置為1,并在pod中引入’iOS-WebP’即可。
服務(wù)端方面,我們采用七牛圖片服務(wù)器,默認(rèn)傳給客戶端的參數(shù)是一張jpg或者png的圖片鏈接,通過(guò)修改url的請(qǐng)求參數(shù)實(shí)現(xiàn)對(duì)WebP圖片的獲取。相關(guān)規(guī)則可以參考七牛開(kāi)發(fā)文檔。
三、具體方案實(shí)現(xiàn)首先考慮,請(qǐng)求的webp圖片是通過(guò)url參數(shù)拼接完成的,所以,需要對(duì)客戶端內(nèi)請(qǐng)求的所有圖片URL做處理,必須全部命中。而且,將來(lái)的緩存也應(yīng)基于此URL進(jìn)行處理,所以,添加一個(gè)NSURL分類(lèi),URL的處理由這個(gè)分類(lèi)統(tǒng)一處理,所有的URL替換最終都會(huì)指向這個(gè)分類(lèi)中的方法,耦合度基本可以將至最低。
@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替換,最終都會(huì)到 - (NSURL *)qd_replaceToWebPURLWithImageWidth:(int)width 這個(gè)方法中來(lái)
下面是條件過(guò)濾,確保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這個(gè)功能,被完全抽離出來(lái),之后的代碼,只需要考慮具體邏輯的問(wèn)題了。
2. Native 圖片請(qǐng)求替換
Native圖片加載使用的SDWebImage,首先需要理解SD的代碼,確定是最終的圖片下載是調(diào)用的哪個(gè)方法
- (id)downloadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionWithFinishedBlock)completedBlock
所有的圖片下載,最終都走到了這個(gè)方法中,所以,替換URL應(yīng)該在這個(gè)方法的最前面實(shí)現(xiàn)。
{ if ([url isKindOfClass:NSString.class]) { url = [NSURL URLWithString:(NSString *)url]; } if (![url isKindOfClass:NSURL.class]) { url = nil; } url = [url qd_replaceToWebPURLWithScreenWidth]; ... ... }
由于在評(píng)估了難度之后,我們果斷地把SDWebImage從Pods中移除,手動(dòng)添加一個(gè)子工程,這樣可以比較方便地修改內(nèi)部實(shí)現(xiàn),而不至于用swizzling這種黑魔法來(lái)修改傳入?yún)?shù)。這個(gè)技能雖然炫酷,然而很多情況下,殺敵一萬(wàn),自損兩萬(wàn),不建議經(jīng)常使用。
因修改了url值,若在上層通過(guò)SDImageCache判斷是否有本地緩存時(shí),也需要對(duì)url先做qd_defultWebPURLCacheKey來(lái)獲取其真實(shí)緩存的key。這一部分比較簡(jiǎn)單。
3. WebView 圖片請(qǐng)求替換
這一部分是這個(gè)方案的難度所在。
webkit內(nèi)核現(xiàn)在都不支持解析WebP格式的圖片,這里主要采用的iOS系統(tǒng)的NSURLProtocol來(lái)替換其網(wǎng)絡(luò)請(qǐng)求(不了解NSURLProtocol,可以動(dòng)動(dòng)自己勤勞的小手Google一下),再將網(wǎng)絡(luò)回包數(shù)據(jù)進(jìn)行轉(zhuǎn)碼成jpg或者png(為了透明度),再返回給webview進(jìn)行渲染的。
友情鏈接,NSURLProtocol用法,大神文章
同樣的,iOS在此處依然不對(duì)gif進(jìn)行任何處理。
另外,NSURLProtocol會(huì)攔截全局的網(wǎng)絡(luò)流量,為避免誤傷,這里需要多帶帶識(shí)別是否是WebView發(fā)起的請(qǐng)求,可以通過(guò)識(shí)別request中的UA是否包含”AppleWebKit”來(lái)實(shí)現(xiàn)。
@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。
下面,會(huì)自動(dòng)調(diào)用startLoading方法,這里采用了一個(gè)非常特別的方式處理
- (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直接接管圖片請(qǐng)求,手動(dòng)finishLoading。
首先需要明確,WebP節(jié)約流量,究竟是怎么樣的原理:
所謂圖片格式,是采用何種解碼編碼方式?jīng)Q定的,所有數(shù)據(jù)最終一定是變成二進(jìn)制數(shù)據(jù),NSData;
既然UIWebView不支持解碼WebP,我們可以讓圖片在網(wǎng)絡(luò)中以WebP格式的NSData傳遞,本地收到data后,解碼成UIWebView可以識(shí)別的UIImage;事實(shí)上,Native方面就是這么做就可以達(dá)到目標(biāo)了,然而在WebView的請(qǐng)求中,無(wú)論我們本地做了何種處理,最終交給WebView的也一定是NSData,所以,需要再把UIImage編碼成jpg或者png(之所以我們沒(méi)有把gif也轉(zhuǎn)WebP,就是因?yàn)閺腤ebP的動(dòng)圖UIImage,轉(zhuǎn)碼成NSData這條路走不通,于是我們放棄了gif轉(zhuǎn)WebP)。
所以,大致的數(shù)據(jù)路徑如下:
本地發(fā)送WebP請(qǐng)求 ---> Server ---> 返回WebP格式Data ---> Data經(jīng)谷歌的WebP decode得到UIImage ---> 將UIImage對(duì)象編碼成JPG或PNG格式NSData ---> 替換本應(yīng)交給WebView的WebP格式Data ---> WebView接收J(rèn)PG或PNG格式Data ---> 渲染圖片
在最開(kāi)始,這里并不是這么寫(xiě)的,當(dāng)時(shí)是在系統(tǒng)的
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
方法中轉(zhuǎn)碼處理。按這個(gè)思路寫(xiě),代碼越寫(xiě)越散,BUG也越來(lái)越多。所以,換了個(gè)思路,既然SD可以支持WebP,為什么不用他來(lái)全面托管呢?
這樣的話,原生請(qǐng)求和WebView的圖片緩存也可以經(jīng)由SD統(tǒng)一起來(lái),所以,這應(yīng)該是一個(gè)好的方案。
這樣的話,WebP的所有請(qǐng)求都已經(jīng)可以處理(wifi預(yù)加載暫時(shí)不管,因?yàn)槭亲约簩?xiě)的downloader,替換URL后直接改把緩存指向修改就可以),之后要處理緩存的問(wèn)題
4. 圖片緩存處理
以前的代碼已經(jīng)實(shí)現(xiàn)了內(nèi)部文章的緩存,包含js、css以及image等。這里通過(guò)NSURLCache來(lái)實(shí)現(xiàn)。相應(yīng)的,基于WebP的圖片緩存的讀取也應(yīng)該在NSURLCache中處理,在先處理完URL后,用新的Key來(lái)進(jìn)行映射。
這里建議所有基于WebView的流量?jī)?yōu)化都最好用UA的判斷包住,避免帶來(lái)問(wèn)題。因?yàn)闊o(wú)論NSURLProtocol還是NSURLCache都是全局網(wǎng)絡(luò)控制。
篇幅略長(zhǎng),具體緩存處理放在下一篇介紹。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/61792.html
摘要:開(kāi)啟驗(yàn)證上傳一張新圖片,使用手安卓版本訪問(wèn)已支持域名的圖片,如果請(qǐng)求帶了,檢查返回圖片格式是否為如果舊的圖片未按預(yù)期返回,返回了或原圖可能是結(jié)點(diǎn)緩存,正常天后過(guò)期回源則會(huì)返回圖片。 對(duì)于圖片較多的網(wǎng)站,本文結(jié)合具體案例給出了如何基于CDN的sharpP自適應(yīng)圖片無(wú)痛接入方案,據(jù)統(tǒng)計(jì)效果可在原圖基礎(chǔ)上節(jié)省60%-75%的流量。作者:陳忱 出處:騰云閣文章 目前移動(dòng)端運(yùn)營(yíng)素材大部分依賴(lài)圖...
摘要:在客戶端基于圖片格式的流量?jī)?yōu)化上這篇文章中,已經(jīng)介紹了格式圖片的下載使用,僅僅只有這樣還遠(yuǎn)遠(yuǎn)不夠,還需要對(duì)已經(jīng)下載的圖片數(shù)據(jù)進(jìn)行緩存。二圖片緩存關(guān)于的緩存,系統(tǒng)提供了一個(gè)類(lèi),。而且,既然是全局影響,肯定要用包起來(lái),防止誤傷其他緩存。 在iOS 客戶端基于 WebP 圖片格式的流量?jī)?yōu)化(上)這篇文章中,已經(jīng)介紹了WebP格式圖片的下載使用,僅僅只有這樣還遠(yuǎn)遠(yuǎn)不夠,還需要對(duì)已經(jīng)下載的圖片數(shù)...
摘要:的支持程度實(shí)際上比你想的可能要好得多。的安卓瀏覽器從版本起開(kāi)始官方支持最初發(fā)布于年月,版本起開(kāi)始部分支持。安卓版從起開(kāi)始支持。而且目前并無(wú)添加支持的任何打算。瀏覽器市場(chǎng)份額截至年月的數(shù)據(jù)顯示,占有約的市場(chǎng)份額,以約位居第二。 本文轉(zhuǎn)載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/862原文:https://optimus.keycdn.com/sup...
閱讀 1565·2023-04-26 01:36
閱讀 2730·2021-10-08 10:05
閱讀 2784·2021-08-05 09:57
閱讀 1544·2019-08-30 15:52
閱讀 1200·2019-08-30 14:12
閱讀 1320·2019-08-30 11:17
閱讀 3110·2019-08-29 13:07
閱讀 2429·2019-08-29 12:35