频繁有客人反馈我们图片在某些地区如:意大利 反馈我们APP图片展示太慢,印象客人下单体验,于是我们开始着手分析。
- 因为我们是混合开发项目,首选确定了iOS不支持,安卓系统4.x以上天然支持不需要处理。
- 目前,后端给前段传递了图片的原始链接,前段根据不同页面展示需要,动态调整参数。 例如:
- 后端提供的参数: http://imgs.intramirror.com/upload/1f53aa5f-c9e0-4c2f-81be-4fe13ec5e3f
- 前段在商详页展示的参数: http://imgs.intramirror.com/upload/1f53aa5f-c9e0-4c2f-81be-4fe13ec5e3f?x-oss-process=image/resize,m_pad,w_750,h_750/quality,Q_100/auto-orient,0/format,jpg
其中粗体部分的两个参数,可以极大的影响图片数据包大小:
- 在quality不变的情况下,只调整format,从jpg改为webp大概能达到数据包减1/3~1/2的效果,视觉质量没有明显变化
- 如果在format不变的情况下,调整quality从100降低到90,大概能达到数据包减1/3~1/2的效果,视觉质量没有明显变化
- 两者同时调整,大概能达到数据包减2/3左右的效果,视觉质量没有明显变化
一番查阅资料
参考链接:参考1 参考2
参考了一些deme发现只能拦截http式的webView,对应加载本地html的方式就无法加载处理,笔者在两者基础上修改实现完成对本地html的支持
以下代码开源在github了 https://github.com/niunaruto/iOSWKWebViewWebp
其实现展示webp格式的原理是:
[NSURLProtocol registerClass:[HybridNSURLProtocol class]];
对APP发起的所有请求进行拦截,并进行重定向
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString);
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:KHybridNSURLProtocolHKey inRequest:request])
return NO;
return YES;
}
return NO;
}
加载完成后
/**
* 加载完毕
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if ([connection.currentRequest.URL.absoluteString hasSuffix:@"webp"]) {
NSData *imageData = self.recData;
UIImage *image = [UIImage sd_imageWithWebPData:self.recData];
imageData = UIImagePNGRepresentation(image);
if (!imageData) {
imageData = UIImageJPEGRepresentation(image, 1);
}
[self.client URLProtocol:self didLoadData:imageData];
[self.client URLProtocolDidFinishLoading:self];
}
}
以下是具体代码:
NSURLProtocol+WKWebVIew.h 文件
#import <Foundation/Foundation.h>
@interface NSURLProtocol (WKWebVIew)
+ (void)wk_registerScheme:(NSString*)scheme;
+ (void)wk_unregisterScheme:(NSString*)scheme;
@end
NSURLProtocol+WKWebVIew.m 文件
#import "NSURLProtocol+WKWebVIew.h"
#import <WebKit/WebKit.h>
//FOUNDATION_STATIC_INLINE 属于属于runtime范畴,你的.m文件需要频繁调用一个函数,可以用static inline来声明。从SDWebImage从get到的。
FOUNDATION_STATIC_INLINE Class ContextControllerClass() {
static Class cls;
if (!cls) {
cls = [[[WKWebView new] valueForKey:@"browsingContextController"] class];
}
return cls;
}
FOUNDATION_STATIC_INLINE SEL RegisterSchemeSelector() {
return NSSelectorFromString(@"registerSchemeForCustomProtocol:");
}
FOUNDATION_STATIC_INLINE SEL UnregisterSchemeSelector() {
return NSSelectorFromString(@"unregisterSchemeForCustomProtocol:");
}
@implementation NSURLProtocol (WebKitSupport)
+ (void)wk_registerScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = RegisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
+ (void)wk_unregisterScheme:(NSString *)scheme {
Class cls = ContextControllerClass();
SEL sel = UnregisterSchemeSelector();
if ([(id)cls respondsToSelector:sel]) {
// 放弃编辑器警告
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
[(id)cls performSelector:sel withObject:scheme];
#pragma clang diagnostic pop
}
}
@end
HybridNSURLProtocol.h
#import <Foundation/Foundation.h>
@interface HybridNSURLProtocol : NSURLProtocol
@end
HybridNSURLProtocol.m
import "HybridNSURLProtocol.h"
#import <UIKit/UIKit.h>
#import <UIImage+WebP.h>
static NSString*const sourUrl = @"https://m.baidu.com/static/index/plus/plus_logo.png";
static NSString*const sourIconUrl = @"http://m.baidu.com/static/search/baiduapp_icon.png";
static NSString*const localUrl = @"http://mecrm.qa.medlinker.net/public/image?id=57026794&certType=workCertPicUrl&time=1484625241";
static NSString* const KHybridNSURLProtocolHKey = @"KHybridNSURLProtocol";
@interface HybridNSURLProtocol ()<NSURLSessionDelegate>
@property (nonnull,strong) NSURLSessionDataTask *task;
@property (strong, nonatomic) NSURLConnection *connection;
@property (strong, nonatomic) NSMutableData *recData;
@end
@implementation HybridNSURLProtocol
- (void)dealloc{
self.recData = nil;
}
+ (BOOL)canInitWithRequest:(NSURLRequest *)request
{
NSLog(@"request.URL.absoluteString = %@",request.URL.absoluteString);
NSString *scheme = [[request URL] scheme];
if ( ([scheme caseInsensitiveCompare:@"http"] == NSOrderedSame ||
[scheme caseInsensitiveCompare:@"https"] == NSOrderedSame ))
{
//看看是否已经处理过了,防止无限循环
if ([NSURLProtocol propertyForKey:KHybridNSURLProtocolHKey inRequest:request])
return NO;
return YES;
}
return NO;
}
+ (NSURLRequest *)canonicalRequestForRequest:(NSURLRequest *)request
{
NSMutableURLRequest *mutableReqeust = [request mutableCopy];
//request截取重定向
if ([request.URL.absoluteString isEqualToString:sourUrl])
{
NSURL* url1 = [NSURL URLWithString:localUrl];
mutableReqeust = [NSMutableURLRequest requestWithURL:url1];
}
return mutableReqeust;
}
+ (BOOL)requestIsCacheEquivalent:(NSURLRequest *)a toRequest:(NSURLRequest *)b
{
return [super requestIsCacheEquivalent:a toRequest:b];
}
- (void)startLoading
{
NSMutableURLRequest *mutableReqeust = [[self request] mutableCopy];
//给我们处理过的请求设置一个标识符, 防止无限循环,
[NSURLProtocol setProperty:@YES forKey:KHybridNSURLProtocolHKey inRequest:mutableReqeust];
//这里最好加上缓存判断,加载本地离线文件, 这个直接简单的例子。
if ([mutableReqeust.URL.absoluteString hasSuffix:@"webp"])
{
NSMutableURLRequest *newRequest = [self cloneRequest:self.request];
NSString *urlString = newRequest.URL.absoluteString;
NSLog(@"######截获WebP url:%@",urlString);
[NSURLProtocol setProperty:@YES forKey:KHybridNSURLProtocolHKey inRequest:newRequest];
[self sendRequest:newRequest];
}
else
{
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:nil];
self.task = [session dataTaskWithRequest:self.request];
[self.task resume];
}
}
- (void)stopLoading
{
if (self.task != nil)
{
[self.task cancel];
}
if (self.connection) {
[self.connection cancel];
}
self.connection = nil;
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition))completionHandler {
[[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageAllowed];
completionHandler(NSURLSessionResponseAllow);
}
- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
[[self client] URLProtocol:self didLoadData:data];
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error {
[self.client URLProtocolDidFinishLoading:self];
}
#pragma mark - NSURLConnectionDataDelegate
/**
* 收到服务器响应
*/
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSURLResponse *returnResponse = response;
[self.client URLProtocol:self didReceiveResponse:returnResponse cacheStoragePolicy:NSURLCacheStorageAllowed];
}
/**
* 接收数据
*/
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (!self.recData) {
self.recData = [NSMutableData new];
}
if (data) {
[self.recData appendData:data];
}
}
/**
* 重定向
*/
- (nullable NSURLRequest *)connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(nullable NSURLResponse *)response{
if (response) {
[self.client URLProtocol:self wasRedirectedToRequest:request redirectResponse:response];
}
return request;
}
/**
* 加载完毕
*/
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
if ([connection.currentRequest.URL.absoluteString hasSuffix:@"webp"]) {
NSData *imageData = self.recData;
UIImage *image = [UIImage sd_imageWithWebPData:self.recData];
imageData = UIImagePNGRepresentation(image);
if (!imageData) {
imageData = UIImageJPEGRepresentation(image, 1);
}
[self.client URLProtocol:self didLoadData:imageData];
[self.client URLProtocolDidFinishLoading:self];
}
}
/**
* 加载失败
*/
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
[self.client URLProtocol:self didFailWithError:error];
}
#pragma mark - 网络请求
- (void)sendRequest:(NSURLRequest *)request{
self.connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
//复制Request对象
- (NSMutableURLRequest *)cloneRequest:(NSURLRequest *)request{
NSMutableURLRequest *newRequest = [NSMutableURLRequest requestWithURL:request.URL cachePolicy:request.cachePolicy timeoutInterval:request.timeoutInterval];
newRequest.allHTTPHeaderFields = request.allHTTPHeaderFields;
[newRequest setValue:@"image/webp,image/*;q=0.8" forHTTPHeaderField:@"Accept"];
if (request.HTTPMethod) {
newRequest.HTTPMethod = request.HTTPMethod;
}
if (request.HTTPBodyStream) {
newRequest.HTTPBodyStream = request.HTTPBodyStream;
}
if (request.HTTPBody) {
newRequest.HTTPBody = request.HTTPBody;
}
newRequest.HTTPShouldUsePipelining = request.HTTPShouldUsePipelining;
newRequest.mainDocumentURL = request.mainDocumentURL;
newRequest.networkServiceType = request.networkServiceType;
return newRequest;
}
@end
以上