HybridKit缓存及流量优化方案
年前,一直在忙项目上的事情也没时间写些东西,趁现在项目还处于空闲期,写下项目中遇到的优化方案。刚解决完发热问题,用户就开始反馈我们的app超级费流量,领导也非常重视。我们所用的图片服务器是又拍云支持尺寸裁剪,但是裁剪后的储存大小还是不可观。正好又拍云支持WebP图片格式,何不尝试下呢?
导致费流量的原因
- 1.HybridKit的UIWebView没有做缓存
- 2.又拍云裁剪后的储存大小还是不够可观
WebP
WebP(发音 weppy,项目主页),是一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式 VP8。根据 Google 的测试,无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使这些 PNG 文件经过其他压缩工具压缩之后,WebP 还是可以减少 28% 的文件大小。
2010 年发布的 WebP 已经不算是新鲜事物了,在 Google 的明星产品如 Youtube、Gmail、Google Play 中都可以看到 WebP 的身影,而 Chrome 网上商店甚至已完全使用了 WebP。国外公司如 Facebook、ebay 和国内公司如腾讯、淘宝、美团等也早已尝鲜。目前 WebP 也在我厂很多的项目中得到应用,如腾讯新闻客户端、腾讯网、QQ空间等,同时也有一些针对 WebP 的图片格式转换工具,如 智图,iSparta 等。
缓存方案
SDWebImage
SDWebImage是iOS开发者经常使用的一个开源框架,这个框架的主要作用是:一个异步下载图片并且支持缓存的UIImageView分类。相信各位iOS开发工程师都不会陌生。
WebP集成方法
1.CocoaPods
pod 'SDWebImage/WebP'
2.手动导入
1.工程引入SDWebImage开源库;
2.引入WebP.framework,下载地址:https://github.com/seanooi/iOS-WebP
3.让SDWebImage支持WebP,设置如下Build Settings -- Preprocessor Macros , add SD_WEBP=1
NSURLCache
- NSURLCache 为应用的 URL 请求提供了内存以及磁盘上的综合缓存机制,作为基础类库 URL 加载的一部分,任何通过 NSURLConnection 加载的请求都将被 NSURLCache 处理。
- 网络缓存减少了需要向服务器发送请求的次数,同时也提升了离线或在低速网络中使用应用的体验。
- 当一个请求完成下载来自服务器的回应,一个缓存的回应将在本地保存。下一次同一个请求再发起时,本地保存的回应就会马上返回,不需要连接服务器。NSURLCache
会 自动 且 透明 地返回回应。 - 为了好好利用 NSURLCache,你需要初始化并设置一个共享的 URL 缓存。在 iOS 中这项工作需要在 -application:didFinishLaunchingWithOptions: 完成
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
NSURLCache *URLCache = [[NSURLCache alloc] initWithMemoryCapacity:4 * 1024 * 1024
diskCapacity:20 * 1024 * 1024
diskPath:nil];
[NSURLCache setSharedURLCache:URLCache];
}
NSURLProtocol
NSURLProtocol可以拦截监听每一个URL Loading System中发出request请求,记住是URL Loading System中那些类发出的请求,也支持AFNetwoking,UIWebView发出的request。如果不是这些类发出的请求,NSURLProtocol就没办法拦截和监听了。
之所以使用NSURLProtocol而不使用NSURLCache的原因是:NSURLProtocol可以拦截UIWebView发出的图片请求,如果检测到时又拍云图片链接,会把请求图片地址更改为WebP格式并使用SDWebImageDownloader来进行图片加载,这是NSURLCache不能实现的。
实现
1.在AppDelegate的-application:didFinishLaunchingWithOptions:方法中进行注册,这样NSURLProtocol才会正常工作。
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[BFNSURLProtocol registerClass:[BFNSURLProtocol class]];
return YES;
}
2.WebView 图片请求替换
webkit内核现在都不支持解析WebP格式的图片,这里主要采用的iOS系统的NSURLProtocol来替换其网络请求(不了解NSURLProtocol,可以动动自己勤劳的小手Google一下),再将网络回包数据进行转码成jpg或者png(为了透明度),再返回给webview进行渲染的。
另外,NSURLProtocol会拦截全局的网络流量,为避免误伤,这里需要单独识别是否是WebView发起的请求,可以通过识别request中的UA是否包含”AppleWebKit”来实现。
@implementation BFNSURLProtocol
+ (BOOL)canInitWithRequest:(NSURLRequest *)request {
// 屏蔽非又拍云地址
if ([[request.URL absoluteString] rangeOfString:@"upaiyun"].location == NSNotFound) {
return NO;
}
//只处理http和https请求
NSString *scheme = [[request URL] scheme];
if (([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame || [scheme caseInsensitiveCompare:@"https"] == NSOrderedSame)) {
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:URLProtocolHandledKey inRequest:request]) {
return NO;
}
NSString *agent = [request valueForHTTPHeaderField:@"User-Agent"];
// 只过滤UIWebview里边的加载图片请求
if ([agent rangeOfString:@"AppleWebKit"].location != NSNotFound && [request.URL isImageURL]) {
return YES;
}
}
return NO;
}
敲黑板,划重点了!划重点了!划重点了!(重要的事情说三遍)
这里可以接管所有WebView中需要替换的图片URL。
下面,会自动调用startLoading方法,利用SDWebImageManager来加载WebP图片,不仅能实现WebP的省流量功能,还能将图片缓存在本地,下次加载同一图片地址的时候又达到了省流量的目的。耶!~
- (void)startLoading {
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//标示改request已经处理过了,防止无限循环
[NSURLProtocol setProperty:@YES forKey:URLProtocolHandledKey inRequest:mutableReqeust];
NSString *URLString = [self.request.URL absoluteString];
// 重定义请求地址 将format为jpg改为webp格式
if ([URLString rangeOfString:@"format"].location == NSNotFound) {
URLString = [[BFWebImageHelper imageStringToURL:URLString width:0 height:0] absoluteString];
}
else if ([URLString rangeOfString:@"format/jpg"].location != NSNotFound) {
URLString = [[BFWebImageHelper imageStringToURL:URLString width:0 height:0] absoluteString];
}
else {
self.connection = [NSURLConnection connectionWithRequest:mutableReqeust delegate:self];
return;
}
NSURL *url = [NSURL URLWithString:URLString];
[[SDWebImageManager sharedManager] downloadImageWithURL:url
options:SDWebImageAllowInvalidSSLCertificates
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
NSData *data;
// 是否以png结尾
if ([imageURL.absoluteString.lowercaseString hasSuffix:@".png"]) {
data = UIImagePNGRepresentation(image);
} else {
data = UIImageJPEGRepresentation(image, 1);
}
if (!self.client) {
return ;
}
[self.client URLProtocol:self didLoadData:data];
[self.client URLProtocolDidFinishLoading:self];
}];
}
- (void)stopLoading {
[self.connection cancel];
self.connection = nil;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request {
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
mutableReqeust = [self redirectHostInRequset:mutableReqeust];
return mutableReqeust;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)equivalent toRequest:(NSURLRequest *)request; {
return [super requestIsCacheEquivalent:equivalent toRequest:request];
}
+ (NSMutableURLRequest *)redirectHostInRequset:(NSMutableURLRequest *)request {
return request;
}
其他代理
#pragma mark - NSURLConnectionDataDelegate
// 请求响应时
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
[self.client URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
}
// 请求接收数据时
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[self.client URLProtocol:self didLoadData:data];
}
// 请求完成的代理
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
[self.client URLProtocolDidFinishLoading:self];
}
// 请求失败&错误的代理
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
[self.client URLProtocol:self didFailWithError:error];
}
PS:流程图
流程图链接processon
总结
在使用了Webp之后,经QA同事的多次测试,同样一套步骤下比未优化前流量节省了47%,使用NSURLProtocol缓存方案后,在WebP优化基础上又优化了12%的流量。其实NSURLProtocol的功能远远不止这些,NSURLProtocol就是一个黑魔法,有兴趣的童鞋可以深入研究一下。今天就写到这里吧。