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。

[NSURLProtocol wkRegisterScheme:@"http"];
[NSURLProtocol wkRegisterScheme:@"https"];

WKURLSchemeHandler是iOS11之后才支持的WK资源拦截能力,WKURLSchemeHandler的具体使用可参考博客:WKURLScheme资源拦截-分析应用WKURLScheme资源拦截-细节处理

拦截Http请求也存在Cookie和Body丢失的问题,处理方式与URLProtocol类似,WKURLSchemeHandler + AjaxHook的具体应用可以参考Git源码-MKWebResources

- (void)setURLSchemeWithConfiguration:(WKWebViewConfiguration *)configuration {
    // 设置http、https的URLScheme
    if (@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_route 的内容格式如下:

{
  "urlPath": {
    "localPath" : "相对路劲",
    "md5" : "文件验签,资源链接有带验签可以忽略"
  }
}
/* 先hitRule命中资源包,再通过resourceId获取资源包的文件路由表 */

本地资源文件的获取与校验:

  • 遍历本地资源信息表,通过hitRule命中具体的资源包
  • 根据resrouceId获取对应资源包的文件路由表
  • 用urlPath取出localPath,与resourcePath合成绝对路劲
  • 获取资源文件数据,用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配置:

<!DOCTYPE html><html><head><meta charset=utf-8><meta name=format-detection content="telephone=no"><meta name=viewport content="width=device-width,user-scalable=no,initial-scale=1,minimum-scale=1,maximum-scale=1,viewport-fit=cover"><title>pluto-h5</title>
<link rel="shortcut icon" href=../favicon.ico>
<link href=../css/app.6abe8eefcc9a65195cb591192f24cbe3.css rel=stylesheet></head><body><div id=app></div>
<script type=text/javascript src=../js/manifest.23dab50270570794ea32.js></script>
<script type=text/javascript src=../js/vendor.47866ead83e2f6d0de33.js></script>
<script type=text/javascript src=../js/app.9e1efd778583cb67d137.js></script>
</body></html>

 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的网络请求

猜你喜欢

转载自blog.csdn.net/z119901214/article/details/103414706