前言
我们知道混合开发的模式现在主要分为两种,H5工程师利用某些工具如DCLOUD产品、codorva+phonegap等等来开发一个外嵌native壳子的混合app
还有就是应用比较广泛的,有native开发工程师和H5工程师一起写作开发的应用,在native的webview里嵌入H5页面,当然只是部分界面这么做,这样做的好处就是效率高,开发成本和维护成本都比较低,较为轻量,但是有一个问题不可避免的会出现,就是js和native的交互
native与js交互部分等详细内容请移步这里: 简书资源 掘金资源
1.NativeAPP(原生APP)
是一款纯工具类的APP应用 例如 微信客户端
特点?
开发: 由原生开发人员进行开发的(IOS 苹果系统 ) (Android 安卓系统)
语言: IOS (XML + Objective-c 、swift) Android (XML + Java+sdk)
更新维护 打补丁包 app store 里面下载更新
使用:必须下载之后才可以使用
优点? 运行稳定 用户粘度高 网络依赖性不是非常强 安装之后几乎不会卸载 流畅度很高 用户体验强 可以调用原生功能
缺点?不能跨平台开发 开发两套代码(Android / IOS) 必须重新发布版本,让用户下载
2.WebApp(移动M站)
凡是可以在移动端浏览器里面打开的网页都称之为WebApp
特点:
开发:WEB前端开发工程师
语言:html5+css3+js(jquery/zepto/vue/react等)
更新维护: 直接无痕更新。(需要注意:浏览器缓存的问题 基于文件路径进行缓存的!)
=> 快捷键 : ctrl + F5 清除浏览器缓存 重新发起http请求
使用:不需要下载,通过手机浏览器直接打开指定的网页访问即可。
优点?可以跨平台 开发成本低、使用成本低、维护成本低
缺点?不稳定 对网络依赖性强 流畅度差 用户体验差 不能够调用设备的原生功能
3.HybirdApp (混合APP)
核心技术: 在native APP中,提供了一个webview,可以在webview里面内嵌h5页面即可。
开发模式:
1)原生主导的开发模式
这款app大部分的界面与功能都是由原生主导开发的,但是部分经常活动的、经常要变动的页面又H5内嵌的页面去实现的。
检测一下哪些是原生应用,也就是说看一下里面是否有内嵌的H5页面。
1.可以查看同类型的安装包的体积大小,小的话就是hybird应用。
(jd taobao)
2.android部分手机可以开启开发调试模式,显示出来布局界面。整个手机都会被线条包裹。
native那部分就会被划分成小模块进行包裹。
因为有些页面,可能没过几天就需要去更新改变。如activity活动的页面,如果让原生写的话,花费很长的事件,但是如果采用webview内嵌H5也没得话,效率就会提升很多。
h5页面可以内嵌到webview窗口中,这样可以提高开发效率,并且很多公司都在采用这种模式。
例如美团,我们可以在美团app中,关于猫眼的这一部分的业务逻辑,可以通过嵌入早已经开发好的webapp实现。对于猫眼这一部分的业务逻辑就不需要重新的进行构建了。
实现原生与H5交互之前,需要掌握如何判断机型!(判断当前的操作系统)
window.navigator.userAgent 可以获取当前设备的操作系统
4、业务逻辑:
H5前端页面内嵌入UIWebview里面去之后,就需要跟原生的人员进行交互。
h5的item点击之后,实现native的界面切换
native界面会给H5的页面传入一些参数,H5页面可以根据传递来的参数进行数据请求
点击立即购票,需要原生那边提供一些用户登录的方法,如果用户登录...
2)H5主导的开发模式
只需要H5前端开发工程师,借助一些封装好的工具来实现应用的打包与调用原生设备的功能。
Dclound(数字天堂) Hbuilder(云端打包) h5+ runtime mui ui组件库
APIClound
phoneGap+cordova (java android sdk )
ios (mac电脑 oc/swift xcode )
测试一下 h5前端写的webapp到底有没有问题
本地测试的话 localhost需要改一下 因为代表本地的意思,“我” 我是一头猪?
测试 需要电脑与手机处于同一个网段下才可以进行访问。
5、window.navigator.userAgent 获取当前设备的操作系统 (window/android/ios)
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36"
6、做一个效果:根据设备的操作系统切换到pc/移动端页面
const express = require('express')
const app = express()
//设置静态资源
app.use(express.static('public'),{
maxage:'2h'
})
app.get('/',(req,res,next)=>{
let Agent = req.headers["user-agent"];
if(/window/.test(Agent)){ //判断为pc端
res.sendFile(__dirname+"/pc.html")
}else{ //判断为移动端
res.sendFile(__dirname+"mobile.html")
}
})
app.listen(3000,()=>console.log('Example app listening on port 3000!'))
一、hybrid开发
1、原生调用js(h5)
Native(Objective-C或Swift)调用Javascript方法
Native调用Javascript语言,是通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法来实现的,该方法返回js脚本的执行结果。
// Swift
webview.stringByEvaluatingJavaScriptFromString("alert(1)")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
从上面代码可以看出它其实就是调用了window下的一个对象,如果我们要让native来调用我们js写的方法,那这个方法就要在window下能访问到。但从全局考虑,我们只要暴露一个对象如JSBridge对native调用就好了,所以在这里可以对native的代码做一个简单的封装:
//下面为伪代码
webview.setDataToJs(somedata);
webview.setDataToJs = function(data) {
webview.stringByEvaluatingJavaScriptFromString("JSBridge.trigger(event, data)")
}
另外:在android中,native与js的通讯方式与ios类似
2、js(H5)调用原生ios Javascript -> OC/Swift
Javascript调用Native,并没有现成的API可以直接拿来用,而是需要间接地通过一些方法来实现。UIWebView有个特性:在UIWebView内发起的所有网络请求,都可以通过delegate函数在Native层得到通知。这样,我们就可以在UIWebView内发起一个自定义的网络请求,通常是这样的格式:
jsbridge://methodName?param1=value1¶m2=value2
发起这样一个网络请求有两种方式:
1)通过localtion.href;
2)通过iframe方式;
通过location.href有个问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。
使用iframe方式,以唤起Native APP的分享组件为例,简单的封闭如下:
var url = 'jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
3、js调用原生ios:JavaScriptCore
定义好JS需要调用的方法,例如JS要调用share方法: 则可以在UIWebView加载url完成后,在其代理方法中添加要调用的share方法 这样的话web页面中就可以直接使用到这个方法:
function secondClick() {
share('分享的标题','分享的内容','图片地址');
}
<button type="button" onclick="secondClick()">Click Me!</button>
在iOS 7之后,apple添加了一个新的库JavaScriptCore
JSContext *context = [self.webView valueForKeyPath:@"documentView.webView.mainFrame.javaScriptContext"];
NSString *textJS = @"showAlert('这里是JS中alert弹出的message')";
[context evaluateScript:textJS];
4、javascript调用native Android方式
目前在android中有三种调用native的方式:
(1)schema
通过schema方式,使用shouldOverrideUrlLoading方法对url协议进行解析。这种js的调用方式与ios的一样,使用iframe来调用native代码。
(2)webview中直接注入原生js
通过在webview页面里直接注入原生js代码方式,使用addJavascriptInterface方法来实现。
在android里实现如下:
class JSInterface {
@JavascriptInterface //注意这个代码一定要加上
public String getUserData() {
return "UserData";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");
alert(AndroidJS.getUserData()) //UserDate
上面的代码就是在页面的window对象里注入了AndroidJS对象。在js里可以直接调用
(3)使用prompt,console.log,alert方式
使用prompt,console.log,alert方式,这三个方法对js里是属性原生的,在android webview这一层是可以重写这三个方法的。一般我们使用prompt,因为这个在js里使用的不多,用来和native通讯副作用比较少。
class YouzanWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
// 这里就可以对js的prompt进行处理,通过result返回结果
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
}
@Override
public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
}
}
总结
1、OC/SWIFT调用js
直接用一些方法执行我们js中的一些语句,也就是说,我们最好定义一些对象,上面放着一些方法准备被native调用,当然也就可以在这些方法里传点参数啥的给咱们了
2、js调用ios
咱们可以整一个请求发出去,这个请求呢会被native给拦截到,他就知道啥意思了 比如,我们可以通过 location.href=A://b=1&c=2&d=3 当然这里的A、b、c、d都要商量好,bcd就是传参数 但是location.href只能发一次,所以我们可以用iframe去发,发完了给iframe干掉就可以了
3、android 调用 js 和oc、swift一样
4、js调用Android
1.也跟调用ios一样,搞个请求,用个iframe 2.Android能想办法给咱的window对象上挂个东西,比如JSBridge啥的然后咱直接调这个玩意的方法就行了 3.他们能把咱的prompt、console.log、alert给重写,也就是说我们用alert已经不能弹出了,反而能给Android传参数了,但是一般不会重写alert,重写的都是不怎么用的prompt
二、混合开发demo
1、静态缓存的小案例 - express.static
express官方文档的Hello World案例
cnpm init -y 创建package.json配置文件
cnpm i express -S
nodemon './server,js' => localhost:3000
server.js的配置
var express = require('express');
var app = express();
/*
直接输出Hello World
app.get('/',(req,res)=>res.send('Hello World'))
*/
//设置静态资源 express.static的缓存 并设置缓存时间为2h
app.use(express.static('public',{
maxAge:'2h'
}));
app.get('/',(req,res,next)=>{
//指定访问相对应的文件
res.sendFile(__dirname + "/index.html")
})
app.listen(3000, function () {
console.log('Example app listening on port 3000!');
});
在index.html中引用css样式即可,来验证样式是否被缓存
<link rel="stylesheet" href="/css/index.css>
此时我们发现,css样式已经被缓存,如何解决express-static缓存问题?
添加唯一的参数即可
就像我们在打包文件上线的时候,会默认给静态文件添加唯一的标识符(不一定是id),便于区分
<link rel="stylesheet" href="/css/index.css?id=1">
清除缓存快捷键:Ctrl + F5
2、判断pc、mobile端进行页面的切换
首先我们可以在
www.baidu.com页面下的network中的Header请求头中得知user-agent
通过如下代码就可以判断
window.navigator.userAgent
判断条件进行切换
app.get('/',(req,res,next)=>{
//?为什么在这里需要用数组的方式进行取值,难道Header请求头的数据是数组的形式?
let Agent = req.headers["user-agent"];
console.log(req.headers["user-agent"])
if(/Windows/.test(Agent)){
res.sendFile(__dirname + "/pc.html")
}else{
res.sendFile(__dirname + "/mobile.html")
}
})
3、实现一些HyBridge的逻辑操作
3-1 h5的item点击之后,实现native的界面切换 (JS调用原生)
3-2 native界面会给H5的页面传入一些参数,H5页面可以根据传递来的参数进行数据请求(原生调用JS)
原生代码:原生给H5传递id参数 webview.stringByEvaluatingJavaScriptFromString("JSBridge.getIdFromWebview(28156)")
------------------------
var JSBridge = {
getIdFromWebview(id){
getDetailById(id)
}
}
function getDetailById(id){
//进行异步请求
axios.get(url,{params:{id}}).then(res=>{
console.log(res)
})
}
3-3 点击立即购票,需要原生那边提供一些用户登录的方法,如果用户登录...
4、使用Hbuilder创建MUI项目demo(Hybrid)
首先创建一个mui-demo的案例 我们可以参考其中的组件示例代码
三、Hybird混合开发
目前市场上面三类主流APP:
1.NativeAPP(原生APP)
是一款纯工具类的APP应用 例如 微信客户端
特点
开发: 由原生开发人员进行开发的(IOS 苹果系统 ) (Android 安卓系统)
语言: IOS (XML + Objective-c 、swift) Android (XML + Java+sdk)
更新维护 打补丁包 app store 里面下载更新
使用:必须下载之后才可以使用
优缺点
优点? 运行稳定 用户粘度高 网络依赖性不是非常强 安装之后几乎不会卸载 流畅度很高 用户体验强 可以调用原生功能
缺点?不能跨平台开发 开发两套代码(Android / IOS) 必须重新发布版本,让用户下载
2.WebApp(移动M站)
凡是可以在移动端浏览器里面打开的网页都称之为WebApp
特点
开发:WEB前端开发工程师
语言:html5+css3+js(jquery/zepto/vue/react等)
更新维护: 直接无痕更新。(需要注意:浏览器缓存的问题 基于文件路径进行缓存的!)
=> 快捷键 : ctrl + F5 清除浏览器缓存 重新发起http请求
使用:不需要下载,通过手机浏览器直接打开指定的网页访问即可。
优缺点
优点?可以跨平台,开发成本低、使用成本低、维护成本低
缺点?不稳定,对网络依赖性强,流畅度差,用户体验差,不能够调用设备的原生功能。
3.HybirdApp (混合APP)
核心技术: 在native APP中,提供了一个webview,可以在webview里面内嵌h5页面即可。
开发模式:
(1)原生主导的开发模式
这款app大部分的界面与功能都是由原生主导开发的,但是部分经常活动的、经常要变动的页面又H5内嵌的页面去实现的。
检测一下哪些是原生应用,也就是说看一下里面是否有内嵌的H5页面。
1.可以查看同类型的安装包的体积大小,小的话就是hybird应用。
(jd taobao)
2.android部分手机可以开启开发调试模式,显示出来布局界面。整个手机都会被线条包裹。
native那部分就会被划分成小模块进行包裹。
(2)为什么要用HybirdApp这种开发模式?
因为有些页面,可能没过几天就需要去更新改变。如activity活动的页面,如果让原生写的话,花费很长的事件,但是如果采用webview内嵌H5也没得话,效率就会提升很多。
h5页面可以内嵌到webview窗口中,这样可以提高开发效率,并且很多公司都在采用这种模式。
例如美团,我们可以在美团app中,关于猫眼的这一部分的业务逻辑,可以通过嵌入早已经开发好的webapp实现。对于猫眼这一部分的业务逻辑就不需要重新的进行构建了。
(3)实现原生与H5交互之前,需要掌握如何判断当前操作系统
window.navigator.userAgent 可以获取当前设备的操作系统
4、业务逻辑:
(1)H5前端页面内嵌入UIWebview里面去之后,就需要跟原生的人员进行交互。
h5的item点击之后,实现native的界面切换
native界面会给H5的页面传入一些参数,H5页面可以根据传递来的参数进行数据请求
点击立即购票,需要原生那边提供一些用户登录的方法,如果用户登录...
(2)H5主导的开发模式
只需要H5前端开发工程师,借助一些封装好的工具来实现应用的打包与调用原生设备的功能。
Dclound(数字天堂) Hbuilder(云端打包) h5+ runtime mui ui组件库
APIClound
phoneGap+cordova (java android sdk )
ios (mac电脑 oc/swift xcode )
测试一下 h5前端写的webapp到底有没有问题
本地测试的话 localhost需要改一下 因为代表本地的意思,
测试 需要电脑与手机处于同一个网段下才可以进行访问。
5、自己总结
(1)window.navigator.userAgent 获取当前设备的操作系统 (window/android/ios)
"Mozilla/5.0 (iPhone; CPU iPhone OS 11_0 like Mac OS X) AppleWebKit/604.1.38 (KHTML, like Gecko) Version/11.0 Mobile/15A372 Safari/604.1"
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Safari/537.36"
"Mozilla/5.0 (Linux; Android 8.0.0; Pixel 2 XL Build/OPD1.170816.004) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/75.0.3770.142 Mobile Safari/537.36"
(2)做一个效果:根据设备的操作系统切换到pc/移动端页面
const express = require('express')
const app = express()
//设置静态资源
app.use(express.static('public'),{
maxage:'2h'
})
app.get('/',(req,res,next)=>{
let Agent = req.headers["user-agent"];
if(/window/.test(Agent)){ //判断为pc端
res.sendFile(__dirname+"/pc.html")
}else{ //判断为移动端
res.sendFile(__dirname+"mobile.html")
}
})
app.listen(3000,()=>console.log('Example app listening on port 3000!'))
(3)原生native调用H5(JS)
Native(Objective-C或Swift)调用Javascript语言,是通过UIWebView组件的stringByEvaluatingJavaScriptFromString方法来实现的,该方法返回JS脚本的执行结果。
// Swift
webview.stringByEvaluatingJavaScriptFromString("alert(1)")
// OC
[webView stringByEvaluatingJavaScriptFromString:@"Math.random();"];
从上面代码可以看出它其实就是调用了window下的一个对象,如果我们要让native来调用我们js写的方法,那这个方法就要在window下能访问到。但从全局考虑,我们只要暴露一个对象如JSBridge对native调用就好了,所以在这里可以对native的代码做一个简单的封装
原生代码:原生给H5传递id参数 webview.stringByEvaluatingJavaScriptFromString("JSBridge.getIdFromWebview(28156)")
var JSBridge = {
getIdFromWebview(id){
getDetailById(id)
}
}
function getDetailById(id){
//进行异步请求
axios.get(url,{params:{id}}).then(res=>{
console.log(res)
})
}
另外:在android中,native与js的通讯方式与ios类似
=>native界面会给H5的页面传入一些参数,H5页面可以根据传递来的参数进行数据请求
(4)H5(js)调用Native IOS(实现点击item)让原生界面实现切换效果
JavaScript -> OC/Swift
Javascript调用Native,并没有现成的API可以直接拿来用,而是需要间接地通过一些方法来实现。UIWebView有个特性:在UIWebView内发起的所有网络请求,都可以通过delegate函数在Native层得到通知。这样,我们就可以在UIWebView内发起一个自定义的网络请求,通常是这样的格式:
jsbridge://methodName?param1=value1¶m2=value2
发起这样一个网络请求有两种方式:
(4-1)通过location.href;
通过location.href有个问题,就是如果我们连续多次修改window.location.href的值,在Native层只能接收到最后一次请求,前面的请求都会被忽略掉。
(4-2)通过iframe方式
使用iframe方式,以唤起Native App 的分享组件为例,简单的封装如下:
var url = 'jsbridge://doAction?title=分享标题&desc=分享描述&link=http%3A%2F%2Fwww.baidu.com';
var iframe = document.createElement('iframe');
iframe.style.width = '1px';
iframe.style.height = '1px';
iframe.style.display = 'none';
iframe.src = url;
document.body.appendChild(iframe);
setTimeout(function() {
iframe.remove();
}, 100);
(4-3)JavaScriptCore
javascriptCore方式:原生那边封装好了一些方法(对象),挂载到window对象,比如有一个moxiu对象,我们就可以通过moxiu.方法进行调用原生功能
function secondClick() {
share('分享的标题','分享的内容','图片地址');
}
...
<button type="button" onclick="secondClick()">Click Me!</button>
==【注】js(H5)调用Android的前两种方法与调用ios的方式相同location.href,iframe==
(5)H5(JavaScript)调用native Android方式
目前在Android中有三种调用native的方式:
(5-1)schema (iframe方式)
通过schema方式,使用shouldOverrideUrlLoading方法对url协议进行解析。这种js的调用方式与ios的一样,使用iframe来调用native代码。
(5-2)webview中直接注入原生JS
通过在webview页面里直接注入原生js代码方式,使用addJavascriptInterface方法来实现。
在Android中实现如下
class JSInterface {
@JavascriptInterface //注意这个代码一定要加上
public String getUserData() {
return "UserData";
}
}
webView.addJavascriptInterface(new JSInterface(), "AndroidJS");
alert(AndroidJS.getUserData()) //UserDate
【注】上面的代码就是在页面的window对象里注入了AndroidJS对象,在JS里可以直接调用
(5-3)使用prompt,console.log,alert方式
使用prompt,console.log,alert方式,这三个方法对js里是属性原生的,在android webview这一层是可以重写这三个方法的。一般我们使用prompt,因为这个在js里使用的不多,用来和native通讯副作用比较少。
代码:
class YouzanWebChromeClient extends WebChromeClient {
@Override
public boolean onJsPrompt(
WebView view,
String url,
String message,
String defaultValue,
JsPromptResult result
){
// 这里就可以对js的prompt进行处理,通过result返回结果
}
@Override
public boolean onConsoleMessage(ConsoleMessage consoleMessage) {
// ... ...
}
@Override
public boolean onJsAlert(
WebView view,
String url,
String message,
JsResult result
){
// ... ...
}
}