如题,web前端服务器和pc机浏览器或者手机app的交互是怎么样的?各自的工作是什么?前端服务器需要处理什么吗?比如我前端有一段代码用来生成二维码什么的?这个逻辑是在前端服务器生成还是到本地客户的pc机的cpu或者客户的手机cpu上生成?
这个需求背后要解决的问题,实际上是要通过 Web 与原生的交互,让 Web 把图片资源交给原生应用去处理。
iOS7 之后苹果推出 JSCore,通过获取 Web 上下文环境,实现了 App 可以直接调用 JS 方法,或者将 block 赋值给 JS 方法来实现 Web 调用 App 并传值。所以在不考虑安卓的情况下,可以通过 JSCore 实现 Web 与 App 交互。
App 端
// 获取JS上下文
// 获取JS上下文
JSContext *jsContext;
-(void)WebViewDidFinishLoad:(UIWebView *)webView {
jsContext = [_webview valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
}
// App 调 Web insertText 事件
JSValue *funcValue = jsContext[@"insertText"];
[funcValue callWithArguments:@[@"hello web!"]];
// Web 调 App,App注册事件名为callNative
jsContext[@"callNative"] = ^(NSString*str){
NSLog(str);
};
```html
// 接收 App 调用
function insertText(text) {
...
}
// 调用 App
btnNode.onclick = () => {
callNative('hello app!');
}
这种方式使用简单,但大多数情况需要兼容 iOS 与安卓,所以需要找到更合适的方式。
传统方式
苹果推出 JSCore 以前,App 调用 Web 只能通过 WebView 执行 JSString 来实现。而 Web 没法直接调用 App,只能触发特定链接,让 App 在 WebView 代理方法中捕获到这特定链接,从而执行相应操作,间接实现 Web 调 App。这种传统方式也适用于安卓端的的实现。
App 调用 Web
```bash
[_webView stringByEvaluatingJavaScriptFromString:jsString];
这里的 JSString 是一个 JS 方法的调用,这个方法能给 Web 传值的前提是在 Web 端定义一个全局方法如:
function handlerMessageFromApp(data) {...}
那 App 端需要去拼接出这个 JSString 然后再调用 Webview 的 stringByEvaluatingJavaScriptFromString 方法:
NSString* jsString = [NSString stringWithFormat:@"handlerMessageFromApp('%@');", messageJSON];
[_webView stringByEvaluatingJavaScriptFromString:jsString];
App 调用 Web 就是利用这种 Webview 去执行一个 JS 方法的方式去实现。JS 方法可以直接返回值给 App,但通常情况下 Web 端收到 App 的消息会去进行一些异步操作,这样在 handlerMessageFromApp 中直接返回就不太合适了。此时需要有另外一个流程去保证 Web 端把消息传递给 App,也就是下面会说的 Web 调 App。
```Web 调用 App
Web 调 App 则需要双方事先沟通定好协议。比如如果要点击 Web 页面链接跳转到 App 主页,那可以将协议的名称定为 xr://home,HTML 内容为 <a href="xr://home">查看主页>></a>,点击链接就会发生 url 的改变,同时原生 WebView 的代理方法也会被触发。通过在代理方法中监听 url 的变化实现约定的协议。
// 在 webview 代理方法中处理
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType {
NSURL *url = [request URL];
if (url == "xr://home") {...}
}
如果基于这种方式让 Web 给 App 传值可以有两种方式:
直接在协议后面拼接参数如 xr://message?name=carson,这种方式适合于简单的值传递,但对于复杂结构的数据传递这种方式不合适。
Web 端 与 App 端定好消息协议如:xr://message,Web 端要发送消息给 App 端时触发该协议,同时将要传递的值放在页面上,App 端监听到该协议变化时从 Web 页面上取值。这种方式适合传递复杂数据。
现实开发中的传值往往都是复杂结构的,所以我们选择第2种方式去完成 Web 端调用 App 端。
function getMessageFromWeb() {
return messageObject;
}
基于前面的经验在 Web 端实现方法 getMessageFromWeb,这个方法负责返回要传递给 App 的值。
id messages = [_webView stringByEvaluatingJavaScriptFromString:@"getMessageFromWeb()"];
当特定的消息协议被触发时 App 通过 Webview 执行带有返回值的 JS 方法拿到数据,这就间接实现 Web 调用 App 并传值的原理。
但这种方式 Web 端每次调用时都需要触发特定协议,同时将全局变量 messageObject 赋值。我们往往希望有一个抽象层来做这些事情,每当调用的时候作为调用方最好是能一个方法传递消息名称与消息内容就行了,接收方也只需要按消息名称接收消息内容。Web 端与 App 端都应该具备这样一个抽象层,这样具体的两端交互就简化成了一端只管调用另一端只管接收。有了这样一个抽象层再来看 Web 端调 App 端就好比:
W 委托 B 发消息给 A 说有包糖要给他,此时 W 已经把糖交给了 B。
B 发出通知给 A。
A 收到通知后跑过来从 B 这里拿走了糖。
这样一个单向的 W 把消息发送给 A 就完成了,原理还是基于前面 Web 调用 App 的原理。因为有 B 的存在此时 W 要做的事情少了很多。
完整的调用
上面的过程只是最简单的情况,是一个单向的,正常情况下 W 给 A 送了包糖或许还想知道 A 什么时候吃完,吃完了 W 可以再去买。
W 委托 B 发消息给 A 说有包糖要给他,此时 W 已经把糖交给了 B。
B 发出通知给 A。
A 收到通知后跑过来从 B 这里拿走了糖。
A 吃完糖后再通知到 B 说他吃完了。
B 最后通知到 W,W 再去买糖。
步骤4与步骤5代表 App 回调 Web。W 最后再买糖相当于回调函数的实现,这是在 W 送糖的时候就已经决定的事情。 因为整个一去一回的过程并非同步,所以这个地方就需要处理好这样一个映射:消息 —> 回调处理函数。也就是说每条发送的消息对应上各自的回调处理函数,这就需要 B 去维护这样一个映射关系。B 给要发送的消息加上 callbackId,同时以 { callbackId: callbackHandler } 的方式将回调处理函数存储起来。A 收到了有 callbackId 的消息再响应时又回继续带上这个 callbackId,最后 B 按照 callbackId 找到回调处理函数去执行。这样才是一个完整的带有回调处理的 Web 调用 App 并传值。
所以关键任务是去实现 B 这样一个抽象层,B 的任务很重,它是一个 Web 端与 App 端交互的桥梁,它要具备发送消息提示、存储消息内容、存储消息回调、 接收消息、执行消息回调等功能。
实现这样一个抽象层并不算太难,但重复造轮子的事就不做了,前面描述的过程也正是参照第三方库 WebViewJavascriptBridge 的实现,B 也正是这个框架的核心 bridge,其整个实现基于观察者模式,在最基本的原理上加上了一些封装,抽象出一层 bridge 负责两端的交互,最终暴露给开发者的只有简单的 API(初始化、注册、调用)。
首先,你的网站、服务、API等需要进行终端计算、处理、存储的内容,都是放到服务器的,服务器需要根据你的项目或者说编程语言,来架设web服务器,类似于IIS,tomcat等等。
PC浏览器、手机浏览器等是通过外网IP(域名)来定向到你架设好的web服务器上,从而访问你的网站或者服务。
手机APP的展示是基于Android、IOS的构建,而数据的交互是通过外网IP(域名))来定向到你架设好的web服务器的API上,它访问的是你的提供数据或者处理的接口。
你这个思维很混乱