H5资源本地化策略 - iOS
一、资源拦截/映射
为了增强用户浏览H5页面的体验,减少页面白屏时间,实现 js、css、image 等资源文件,以及页面html文件的本地映射(非首次打开wkwebview本身有302缓存机制,不包含html加载)。
1、资源拦截的过程
- web端发起资源加载的请求(js、css、image)
- 使用NSURLProtocol / WKURLSchemeHandler实现资源请求的拦截
- 根据资源链接判读文件是否缓存于本地
- 匹配到有效的资源,读取文件后回传给web端
- 没有匹配到有效的资源,下载文件后回传给web端
WKWebView需要注册scheme才能实现URLProtocol的拦截。
@implementation NSURLProtocol (WKWebKit)/* WKWebView注册Scheme for URLProtocol;WKBrowsingContextController为私有API,可以通过Base64编码来绕过私有API的检查 */
+ (void)wkRegisterScheme:(NSString *)scheme {Class cls = NSClassFromString(@"WKBrowsingContextController");SEL sel = NSSelectorFromString(@"registerSchemeForCustomProtocol:");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
自定义scheme需要前端额外改变原有的scheme,对原有工程的侵入性比较大。
[NSURLProtocol wkRegisterScheme:@"localScheme"];
WKWebView注册 http / https 的scheme实现URLProtocol的拦截,会导致Http请求的Body和Cookie丢失。Hook Ajax请求,通过JSBridge的方式提前将HTTPBody传给Native存储,避免HTTPBody丢失的情况;Hook document.cookie方法,将cookie信息同步于NSHTTPCookieStorage。具体实现可参照Git项目:Ajax-hook、KKJSBridge。
[NSURLProtocol wkRegisterScheme:@"http"];
[NSURLProtocol wkRegisterScheme:@"https"];
WKURLSchemeHandler是iOS11之后才支持的WK资源拦截能力,拦截Http请求也存在Cookie和Body丢失的问题,处理方式与URLProtocol类似。
WKURLSchemeHandler的具体使用可参考博客:WKURLScheme资源拦截-分析应用、WKURLScheme资源拦截-细节处理。
- (void)setURLSchemeWithConfiguration:(WKWebViewConfiguration *)configuration {// 设置http、https的URLSchemeif (@available(iOS 11.0, *)) {WFURLSchemeHandler *schemeHandler = [[WFURLSchemeHandler alloc] init]; // webView.configuration持有该实例if (![configuration urlSchemeHandlerForURLScheme:@"http"] && ![configuration urlSchemeHandlerForURLScheme:@"https"]) {[configuration setURLSchemeHandler:schemeHandler forURLScheme:@"http"];[configuration setURLSchemeHandler:schemeHandler forURLScheme:@"https"];}}
}
URLScheme设置为 http / https 会导致crash,可以通过Hook WKWebview的handlesURLScheme方法来解决该问题。
@implementation WKWebView (URLSchemeHandler)+ (void)load {static dispatch_once_t onceToken;dispatch_once(&onceToken, ^{Method originalMethod = class_getClassMethod([self class], @selector(handlesURLScheme:));Method swizzledMethod = class_getClassMethod([self class], @selector(wfHandlesURLScheme:));method_exchangeImplementations(originalMethod, swizzledMethod);});
}+ (BOOL)wfHandlesURLScheme:(NSString *)urlScheme {if ([urlScheme isEqualToString:@"https"]|| [urlScheme isEqualToString:@"http"]) {return NO;} else {return [self wfHandlesURLScheme:urlScheme];}
}@end
2、拦截/下发相关配置
1)资源拦截开关配置
{"usable" : 1,"resourceBlackList" : [ // 不使用映射的文件列表"https://m.host.com/release/product/detail/res/js/899239823883.js","https://m.host.com/release/activity/detail/res/img/827328788782.jpg",...],"suffixBlackList" : [ // 不做拦截的资源后缀"docx","pdf",...],
}
2)资源包下发配置
根据沙盒webFast目录中的本地资源信息表,获取最新的资源包配置。资源包分位全量包与差分包,全量包是新增/覆盖已有的资源包,差分包则是在原有的资源压缩包的基础上,通过差分算法与patch包合成最新的全量包(如:线上最新资源包的版本为2.6,本地资源包的版本号为2.1只需要下差分包,本地资源包的版本号为1.1则需要下全量包)。
/* 资源包下发配置详情 */
[{"moduleName" : "chat_module,模块名","resourceId" : "123456,资源id","hitRule" : "cres.host/chat_module,资源命中规则", "usable" : "是否使用,1:正常使用","version" : "资源包版本号,1.1","url" : "全量包下载地址","hasPatch" : "是否有差分包,1:有","patchUrl" : "Patch包下载地址"
},
{"moduleName" : "shop_module,模块名","resourceId" : "123457,资源id","hitRule" : "cres.host/shop_module,资源命中规则", "usable" : "是否使用,0:删除已有资源","version" : "资源包版本号,2.2","url" : "全量包下载地址","hasPatch" : "是否有差分包,0:没有","patchUrl" : "Patch包下载地址"
}]
本地资源信息表:resourceId(资源id)、moduleName(模块名)、version(资源版本)、resourcePath(资源存储目录)、fullZipPath(全量包存储路劲)、 accessTime(最近访问时间)
资源命中规则:资源链接命中资源包的规则(资源链接cres.host/chat_module/res/..utils.js,命中规则cres.host/chat_module)
3)资源包更新策略
具体的资源包更新策略如下:
根据不同场景制定不同的异常重试机制,也可以默认不处理。
3、资源包目录结构
1)自定义资源包目录
取决于前端打包能力是否支持,结构图如下(webFast | module | js、css、image、font)
file.json的内容格式如下:
{"fileName": {"path" : "相对路劲","md5" : "文件验签,资源链接有带验签可以忽略"}
}
/* 遍历web目录中的module文件夹,获取对应的file.json内容,存于static NSDictionary* fileRouter */
本地资源文件的获取与校验:
- 资源url根据协定的规则生成 'fileName'
- 通过 'fileName' 从 file.json 中获取文件路劲
- 获取资源文件,用fileData生成文件md5签名
- 两个md5值相比较,验证文件的有效性
2)以资源链接的Host/Path为目录结构
大致结构图如下(webFast | module | res | js | common | utils)
文件链接:http://cres.host/module_name/res/js/common/utils/stream.3e3d3ab3c2ab7412eb923d3ab3c2eb48.js
文件目录:沙盒/Documents/web_resource/cres.host/module_name/res/js/common/utils
- 储存的相对路径为URL-Path,更贴近于前端资源的部署方式
- 不依赖于file.json,直接通过URL-Path去映射,方式更加快捷
通过URL-Path获取文件的相对路劲,生成文件的沙盒路劲,多线程获取对应路径的资源文件,按照约定规则生成文件的md5签名,与URL-Path中的md5签名相比较,验证文件的有效性。
3)两种目录结构的对比
沙盒存储资源的目录结构取决于前端工程的构建打包能力。
- 自定义的目录结构需要下发各个模块的文件路由表,支持html文件本地化
- Host/Path的目录结构则通过资源链接生成本地文件的绝对路劲,不支持html文件本地化(html无法验签)
二、html资源包本地化
类似于浏览器存储H5静态页面的方式,把页面所涉及到的资源文件打包,配置下方到沙盒目录,通过路由配置映射到页面对应的本地html文件,实现H5页面的渲染。最终得达到页面加速的效果,减少了页面白屏时间。
H5资源包本地化实现更加简单,免去了URLProtocol拦截映射的流程,直接加载本地html文件,不过存在外部cdn资源请求跨域问题。
webview的具体页面加载:
NSMutableURLRequest* mURLRequest = [NSMutableURLRequest requestWithURL:@"file://../Documents/web/moulde/html/index.html"];
[self.webView loadRequest:mURLRequest];/* wkwebView默认不允许运行在一个URL环境中的JavaScript访问来自其他URL环境的内容,需要在wkwebView初始化中添加以下配置 */
[configuration.preferences setValue:@YES forKey:@"allowFileAccessFromFileURLs"];
html中的资源文件src配置:
pluto-h5
file.json用于对应module的文件检验,防止资源被篡改:
{"versionName": "1.0.0","pathMap": {"js/vendor.47866ead83e2f6d0de33.js": "47a96eacd8259c4d882da5044ea4b36f"},"md5Map": {"47a96eacd8259c4d882da5044ea4b36f": "js/vendor.47866ead83e2f6d0de33.js"}
}
routes.json用于匹配具体的页面html地址,页面路由配置:
"map": {"http://m.host.com/shopping/product/detail/index.html": "/web/module/product_detail/index.html","http://m.host.com/shopping/order/list/index.html": "/web/module/order_list/index.html"}
相关资料查阅
iOS H5资源本地化方案选型
一站式解决WKWebView各类问题
如何接管WKWebView的网络请求
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
