HybridKit缓存及流量优化方案

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
HybridKit缓存及流量优化方案_第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就是一个黑魔法,有兴趣的童鞋可以深入研究一下。今天就写到这里吧。

你可能感兴趣的:(HybridKit缓存及流量优化方案)