作者 | 李保君
嗨,我是ucself,一名大前端开发工程师。
项目使用技术发展历史
随着大前端技术的快速发展,项目历史包袱的遗留就会产生日益健全的 SDK 和项目框架。以文章的形式回忆和分享项目中使用技术发展要点,也算是工作,学习的产物。
以企鹅医生 App 为例,以下为 App 框架设计发展要点:
原生 Native 技术开发
Native + Hybrid 技术开发
Native + Hybrid + React Native 技术开发
Native + Hybrid + React Native + flutter 技术开发
技术使用不是简单的把各种技术柔和在一个就行了,他们之间需要通信,规则,层次调用。今天先分享一下 Native + Hybrid 的框架设计。
什么是 Hybrid
Hybrid 从外观上来看是一个 Native UI,实则只有一个 WebView 里面访问的是一个使用 HTML5+CSS+JavaScript 做业务开发的 Web UI,即是 Native 的框架加上 Web 的内容开发方式。
Native,Hybrid 对比
Hybrid | Native | |
---|---|---|
开发成本 | 中 | 高 |
维护更新 | 简单 | 复杂 |
市场 | 认可 | 认可 |
体验 | 良 | 优 |
跨平台 | 优 | 差 |
平台特性 | 优 | 优 |
复杂动画 | 差 | 优 |
Hybrid 类型
1、Controller 主体型:整个页面是 Web View,穿插 Native 功能,主要以网页语言编写,这也是项目中绝大多数业务使用类型。
2、View 型:在同一个 View 内,Native View 和 Web View 为同时出现。比如:当 Native 未给 H5 提供复杂的导航栏渐变协议,而 H5 复杂的 UI 已经完成,这时候就需要使用 View 型 Native 来实现导航栏渐变,H5 实现业务。
Hybrid 架构设计
一、架构图
Hybrid 设计主要分为三个模块:H5 模块,Hybrid SDK 模块,Native 模块。如下架构图:
H5:H5 业务模块,主要让 SDK 注入方法/对象,方便 H5 业务调用(回调) SDK 响应的业务功能。
Hybrid SDK:SDK 模块,主要任务是注入 H5,注入 Native 需要调用和回调方法;实现基础服务的协议(如:header,forward,back,modal,dismiss,pageshow,pagehide)。
Native:Native 模块,主要响应 H5 调用 SDK 的时候,SDK 无法满足的协议进行扩展,此模块与 SDK 调用和回调。
二、时序图
时序图可以清楚的了解到业务调用流程关系。如下时序图:
根据时序图从以下几个关键技术点进行分享:
Native 回调事件注入
SDK 离线更新机制
SDK 与 H5 通讯机制
SDK 通讯流程
2.1 Native 回调事件注入
最开始在功能满足业务需求的时候,还未去实现 Native 回调事件注入。
随着业务的发展 H5 页面越来也多,需要 SDK 对 Native 做一些必要的响应,如:
页面加载开始
页面加载结束
页面加载失败
根据业务增加,还可以继续增加回调事件注入
/// 命令扩展
public protocol HybridMethodProtocol {
/// 页面加载失败所需操作
///
/// - Parameter command: 命令对象
func didFailLoad(viewController: HybridViewController)
/// 加载等待开始 业务需要重写
func startWait()
/// 加载等待结束 业务需要重写
func stopWait()
}
2.2 SDK 离线更新机制
在线访问 url 已经满足了业务功能需求,为了响应性能,提高体验 SDK 也做了离线更新机制。
A、获取资源文件压缩包
首先请求 H5 打包生成的 json 数据,并对 json 数据进行分析与本地 json 数据进行对比,如果版本改变则下载 json 数据中的压缩包,解压存储到本地。
{
"name": "healthapp-1.0.2",
"version": "1.0.2",
"appName": "healthapp",
"source": [
{
"id": "5b2a1ff5374a4600d476ec72",
"key": "app-health-1.0.1",
"name": "app/health",
"version": "1.0.1",
"bundle": "http://web-dev.doctorwork.com/ios/resources.zip"
},
{
"id": "5b2a2103374a4600d476ec74",
"key": "rapp-health-1.0.1",
"name": "rapp/health",
"version": "1.0.1",
"bundle": "http://web-dev.doctorwork.com/ios/resources.zip"
}
]
}
B、请求拦截加载本地资源
首先注册拦截协议,用于拦截 url 请求的回调
//设置拦截回调
if HybridConfiguration.default.isRegisterURLProtocol {
URLProtocol.registerClass(HybridURLProtocol.self)
URLProtocol.wk_registerScheme("http")
URLProtocol.wk_registerScheme("https")
}
在拦截回调中进行离线逻辑拦截判断,匹配到本地存在改 url 请求的文件直接返回本地文件 response,如下时序图:
经过对 WKWebview 的测试,WKWebview 包含了内存缓存机制,如果是内存中存在缓存会直接返回内存中的资源。
SDK 与 H5 通讯机制
SDK 与 H5 通讯方式有很多种:
URL scheme
JavaScriptCore
ScriptMessageHandler
分享一下各个方式的机制
A、URL scheme
URL scheme 通讯方式是 Native 端拦截 url 请求通过获取 scheme 名称进行分析处理业务。
js 端发送命令:
loadURL(
"hybridProtocol://shareClick?title=分享的标题&content=分享的内容&url=链接地址&imagePath=图片地址"
);
Native 端进行拦截并响应业务需求
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSURL * url = [request URL];
if ([[url scheme] isEqualToString:@"firstclick"]) {
NSString *host = url.host;
NSArray *params =[url.query componentsSeparatedByString:@"&"];
NSMutableDictionary *tempDic = [NSMutableDictionary dictionary];
for (NSString *paramStr in params) {
NSArray *dicArray = [paramStr componentsSeparatedByString:@"="];
if (dicArray.count > 1) {
NSString *decodeValue = [dicArray[1] stringByReplacingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
[tempDic setObject:decodeValue forKey:dicArray[0]];
}
}
NSLog(@"tempDic:%@",host);
NSLog(@"tempDic:%@",tempDic);
return NO;
}
return YES;
}
B、JavaScriptCore
JavaScriptCore 是 Native 系统提供的 API,向 H5 页面注入方法,H5 执行注入的方法做业务响应。
Native 端获取上下文会话并注入方法到 H5
- (void)addShareWithContext:(JSContext *)context
{
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
context[@"shareClick"] = ^() {
NSArray *args = [JSContext currentArguments];
if (args.count < 3) {
return ;
}
NSString *title = [args[0] toString];
NSString *content = [args[1] toString];
NSString *url = [args[2] toString];
// 在这里执行分享的操作
// 将分享结果返回给js
NSString *jsStr = [NSString stringWithFormat:@"shareResult('%@','%@','%@')",title,content,url];
[[JSContext currentContext] evaluateScript:jsStr];
};
}
H5 端执行注入的方法
shareClick("测试分享的标题", "测试分享的内容", "url=http://www.baidu.com");
C、ScriptMessageHandler
ScriptMessageHandler 也是 Native 系统提供的 API,向 H5 页面注入方法,H5 执行注入的方法做业务响应。经过测试使用区别在于 H5 开始加载的时候 ScriptMessageHandler 就注入了方法
Native 使用 ScriptMessageHandler 方法注入到 H5
[self.webView.configuration.userContentController addScriptMessageHandler:self name:@"shareClick"];
- (void)userContentController:(WKUserContentController *)userContentController didReceiveScriptMessage:(WKScriptMessage *)message
{
NSLog(@"body:%@",message.body);
if ([message.name isEqualToString:@"ScanAction"]) {
NSLog(@"分享业务");
}
}
H5 端执行注入的方法
window.webkit.messageHandlers.Share.postMessage({
title: "测试分享的标题",
content: "测试分享的内容",
url: "url=http://www.baidu.com",
});
综上,Native 与 H5 的通讯方式选择了 messageHandlers 注入方式。以上三种方式 Native 回调 H5 是 都是 webView 执行 js 方法进行回调或者协议中传入执行的回调 js 方法。
SDK 通讯流程
在业务通讯方面设计原则是:SDK 能完成的业务自行完成,不能完成的交给业务 Native 去完成。具体实现逻辑如下时序图:
SDK 业务为功能业务或者依赖 SDK 的业务,如:
Header
Forward
Back
Modal
Dismiss
Pageshow
Pagehide
Native 业务为项目中需要实现的需求业务,如:
Device
Location
Share
Camera
Clipboard
等等
其他实现技术点
文件操作增,删,压缩,解压。
NSURLProtocol
NavigationBarTransition
动态执行方法
ScriptMessageHandler
NSLayoutConstraint
estimatedProgress
还有很多技术实现细节就不一一描述了。
总结
不同的 Native 需要针对不同的平台使用不同的开发语言(如使用 Objective-C、Swift 开发 iOS 应用,使用 Java 等开发 Android 应用),而 Hybrid 可开发能够在不同平台上部署的类原生应用。
由于 Hybrid App 结合了 Native App 良好用户交互体验和 Web App 跨平台开发的优势,非常有利于前端介入,非常适合业务快速迭代,Hybrid App 越来越多公司在使用。
全文完
以下文章您可能也会感兴趣:
我们正在招聘 Java 工程师,欢迎有兴趣的同学投递简历到 rd-hr@xingren.com 。