这篇文章介绍在WKWebView
页面内加载网络图片时的优化方案;相比直接在H5
页面内请求加载一个网络图片,如果能通过原生端的图片框架完成图片的下载与缓存,再把图片数据回传给H5页面显示,那不管是在对缓存的控制
提高加载性能上,还是在对下载到的图片数据进行其他处理的空间上,都会有更大的便捷性和效率;
这里第一个要解决的问题是拦截H5页面内的图片加载请求,然后走原生端上的图片处理逻辑,再把数据回传给H5页面;WKWebView
的WKURLSchemeHandler
机制可以完成针对特定请求的拦截,我们只需要和H5页面内的请求约定一个特定的scheme
即可,原生端在判断到对应的scheme后,即做请求拦截,并在原生端处理完图片的下载、缓存等逻辑后在把图片数据回传给H5页面显示;如下所示:
/// 开启特定scheme请求拦截,并在处理后回传数据给H5页面
- (void)webView:(nonnull WKWebView *)webView startURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(11.0)) {
[super webView:webView startURLSchemeTask:urlSchemeTask];
NSURLComponents *componets = [[NSURLComponents alloc] initWithString:url.absoluteString];
NSString *scheme = componets.scheme;
//判断到特定约定的scheme,开启拦截
if (scheme && [scheme isEqualToString:@"customWebImageScheme"]) {
//开始端上拦截后的图片处理
SDWebImageOptions options = SDWebImageRetryFailed | SDWebImageAvoidDecodeImage;
[[SDWebImageManager sharedManager] loadImageWithURL:realWebPUrl options:options progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
//数据回传给H5页面
[self responseWithUrlSchemeTask:urlSchemeTask mimeType:MIMEType data:data error:error];
}];
return YES;
}
return NO;
}
// 回传给H5页面下载处理后的NSData数据
- (void)responseWithUrlSchemeTask:(id <WKURLSchemeTask>)urlSchemeTask
mimeType:(NSString *)mimeType
data:(NSData *)data
error:(NSError *)error API_AVAILABLE(ios(11.0)) {
if (!urlSchemeTask || !urlSchemeTask.request || !urlSchemeTask.request.URL) {
return;
}
if (error) {
[urlSchemeTask didFailWithError:error];
} else {
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:urlSchemeTask.request.URL
MIMEType:mimeType
expectedContentLength:data.length
textEncodingName:nil];
[urlSchemeTask didReceiveResponse:response];
[urlSchemeTask didReceiveData:data ?: [NSData new]];
[urlSchemeTask didFinish];
}
}
- (void)webView:(nonnull WKWebView *)webView stopURLSchemeTask:(nonnull id<WKURLSchemeTask>)urlSchemeTask API_AVAILABLE(ios(11.0)) {
}
优化过程
在这个过程中,图片的下载缓存处理过程全部都转移到端上的图片处理框架上了,以上代码示例使用的是SDWebImage
(端上有很多其他的图片处理框架、流程基本上是一致的),可以发现,我们回传给H5页面显示的数据只需要NSData类型的数据(如上代码所示: [urlSchemeTask didReceiveData:data ?: [NSData new]];
);
在这种场景下,默认的图片处理框架
并不会只返回我们需要的NSData类型的数据,而是会抛出UIImage、NSData
两个类型的数据对象;也就是在SDWebImage
内部实际上在下载到图片的Data数据后,还做了一层UIImage对象转化的操作 (这一步中还涉及到了图片的解码操作),且SDWebImage
内部的内存缓存实现方式
实际上缓存的是UIImage
对象;这种情况下,我们需要的NSData类型的数据 实际上需要每次访问磁盘缓存才能获取到,这会带来如下几个问题:
- UIImage:NSData转UIImage是不必要的,且这个过程中涉及的图片解码操作是比较耗费性能的
- Cache:内存缓存无法被使用,读缓存时需要磁盘IO,影响效率和性能
- 耗电:频繁的磁盘IO影响耗电;
图片的解码操作
经测试和图片的大小有关,经过简单测试单张图片的解码耗时通常在0.1~1.0ms以内,SDWebImage
内对图片的解码操作如下所示:
在下载完网络图片后,就会在一个子线程中
开始做图片的解码处理,处理完成后再把UIImage和NSData数据一起抛出;
这些操作在这种场景下都是不必要的(我们只需要回传给H5页面NSData数据),因此我们优化的主要方向是怎么取消掉这些不必要的操作,让图片的拦截加载更有效率,提高加载性能的同时又不影响SDWebImage
的默认处理逻辑;总结需要优化的点包括以下几处:
- 转码优化:直接使用下载后的NSData数据,取消掉不需要的图片解码操作
- Cache优化:内存缓存的读取效率通常至少是磁盘的10倍以上;修改内存缓存对象,从UIImage改成NSData;
- 耗电优化:使用内存缓存,避免缓存命中时的频繁IO;
有了特定的优化方向后,我们只需要针对这几个目标对使用到的图片处理框架
做适配改造就可以了,针对性的去处或修改掉我们不适用的操作;与此同时,还需要考虑到在改造时与原框架的兼容性;
与原框架的兼容性主要体现在我们的修改不能影响到默认的图片库功能,原因是在应用内可能还有需要用到框架默认能力的场景;文章最后会给出一个优化后实现的demo,可以直接集成使用;
最后
如果你的应用内拦截H5页面的图片请求的场景并不多,则可以直接使用图片处理框架
的默认能力,在处理图片不多的情况下优化改进带来的提升也许并不必要;我们公司内部在实现小程序引擎
时,使用的是这种拦截方式处理的图片加载;因此对我们来说小程序应用里的很多页面的图片加载请求会很多,在默认使用SDWebImage
框架的端上图片处理时,有必要对这部分加载做一个改进优化;
这里在对SDWebImage
默认的实现方式做一个补充说明,端上的图片处理框架
本质上是从原生端的应用场景出发的,默认的逻辑都是针对原生端的图片加载做的优化;在原生应用上我们主要是通过UIImageView、UIButton
等UI控件来加载显示图片的,这些控件都通过直接设置UIImage对象来加载图片,由此默认的UIImage内存缓存在这种场景下是高效的,图片的解码操作也能避免主线程解码的性能阻塞;但是在与H5的交互上,这些操作的契合程度就不那么高了,才需要我们有针对性的做一些改造优化,以提高图片的加载性能;