摘要:一了解,是一種同時提供了有損壓縮與無損壓縮的圖片文件格式,是新推出的影像技術,它可讓網頁圖檔有效進行壓縮,同時又不影響圖片格式兼容與實際清晰度,進而讓整體網頁下載速度加快。這里建議所有基于的流量優化都最好用的判斷包住,避免帶來問題。
首先,這是一個基于具體業務的組件優化方案,我盡量把業務邏輯從代碼中抽離出來,部分地方代碼可能有刪減。
現在這個方案是用于一個多圖片的新聞類應用,粗略估計過,用戶在瀏覽完第一頁所有新聞(共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都是全局網絡控制。
篇幅略長,具體緩存處理放在下一篇介紹。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/61792.html
摘要:開啟驗證上傳一張新圖片,使用手安卓版本訪問已支持域名的圖片,如果請求帶了,檢查返回圖片格式是否為如果舊的圖片未按預期返回,返回了或原圖可能是結點緩存,正常天后過期回源則會返回圖片。 對于圖片較多的網站,本文結合具體案例給出了如何基于CDN的sharpP自適應圖片無痛接入方案,據統計效果可在原圖基礎上節省60%-75%的流量。作者:陳忱 出處:騰云閣文章 目前移動端運營素材大部分依賴圖...
摘要:在客戶端基于圖片格式的流量優化上這篇文章中,已經介紹了格式圖片的下載使用,僅僅只有這樣還遠遠不夠,還需要對已經下載的圖片數據進行緩存。二圖片緩存關于的緩存,系統提供了一個類,。而且,既然是全局影響,肯定要用包起來,防止誤傷其他緩存。 在iOS 客戶端基于 WebP 圖片格式的流量優化(上)這篇文章中,已經介紹了WebP格式圖片的下載使用,僅僅只有這樣還遠遠不夠,還需要對已經下載的圖片數...
摘要:的支持程度實際上比你想的可能要好得多。的安卓瀏覽器從版本起開始官方支持最初發布于年月,版本起開始部分支持。安卓版從起開始支持。而且目前并無添加支持的任何打算。瀏覽器市場份額截至年月的數據顯示,占有約的市場份額,以約位居第二。 本文轉載自:眾成翻譯譯者:文藺鏈接:http://www.zcfy.cc/article/862原文:https://optimus.keycdn.com/sup...
閱讀 1553·2023-04-26 01:36
閱讀 2719·2021-10-08 10:05
閱讀 2775·2021-08-05 09:57
閱讀 1537·2019-08-30 15:52
閱讀 1193·2019-08-30 14:12
閱讀 1311·2019-08-30 11:17
閱讀 3097·2019-08-29 13:07
閱讀 2415·2019-08-29 12:35