Hybrid

102 阅读17分钟

前言

我们知道混合开发的模式现在主要分为两种,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&param2=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&param2=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
	){
		// ... ...
	}
}