随着 Web 技术和移动设备的飞速发展,各类 APP 层出不穷,极速的业务扩展提升了团队对开发效率的要求,这个时候使用 IOS/Andriod 开发一个 APP 彷佛成本有点太高了,而 H5 的低成本、高效率、跨平台等特性立刻被利用起来造成了一种新的开发模式:Hybrid APP
。javascript
Hybrid 技术已经成为一种最主流最多见的方案。一套好的 Hybrid 架构解决方案能让 App 既能拥有极致的体验和性能,同时也能拥有 Web 技术 灵活的开发模式、跨平台能力以及热更新机制。本文主要是结合我最近开发的一个 Hybrid 项目(https://github.com/Jack-cool/hybrid_jd
),带你们全面了解一下 Hybrid。前端
深刻了解 Hybrid 前,让咱们先来看一下目前市面上比较成熟的混合解决方案。java
这种是市面上大多数 app 采起的方案,也是混合开发最基础的方案。在 webview 的基础上,与原生客户端创建js bridge
桥接,以达到 js 调用Native API
和 Native 执行js
方法的目的。react
目前国内绝大部分的大厂都有一套本身的基于 webview ui 的 hybrid 解决方案,例如微信的JS-SDK
,支付宝的JSAPI
等,经过JSBridge
完成 h5 与 Native 的双向通信,从而赋予 H5 必定程度的原生能力。android
能够简单理解为“跨平台”,如今比较通用的有React Native
,Weex
,Flutter
等。在赋予 H5 原生 API 能力的基础上,进一步经过 JSBridge 将 JS 解析成的虚拟节点数(Virtual DOM)传递到 Native 并使用原生渲染。咱们这里来看下上面提到的这三种:ios
“Learn once, write anywhere”,React Native
采用了 React 的设计模式,但 UI 渲染、动画效果、网络请求等均由原生端实现(因为 JS 是单线程,不大可能处理太多耗时的操做)。开发者编写的 JS 代码,经过 React Native 的中间层转化为原生控件和操做,极大的提升了用户体验。git
React Native
全部的标签都不是真实控件,JS 代码中所写控件的做用,相似 Map 中的 key 值。JS 端经过这个 key 组合的 Dom ,最后 Native 端会解析这个 Dom ,获得对应的 Native 控件渲染,如 Android 中 标签对应 ViewGroup 控件。github
总结下来,就是:React Native 是利用 JS 来调用 Native 端的组件,从而实现相应的功能。web
“Write once, run everywhere”,基于 Vue 设计模式,支持 web、android、ios 三端,原生端一样经过中间层转化,将控件和操做转化为原生逻辑来提高用户体验。编程
在 weex 中,主要包括三大部分:JS Bridge
、Render
、Dom
,JS Bridge 主要用来和 JS 端实现进行双向通讯,好比把 JS 端的 dom 结构传递给 Dom 线程。Dom 主要是用于负责 dom 的解析、映射、添加等等的操做,最后通知 UI 线程更新。而 Render 负责在 UI 线程中对 dom 实现渲染。
和 react native 同样,weex 全部的标签也都不是真实控件,JS 代码中所生成的 dom,最终都是由 Native 端解析,再获得对应的 Native 控件渲染,如 Android 中 标签对应 WXTextView 控件。
Flutter
是谷歌 2018 年发布的跨平台移动 UI 框架。与 react native 和 weex 的经过 Javascript 开发不一样,Flutter 的编程语言是Dart
,因此执行时并不须要 Javascript 引擎,但实际效果最终也经过原生渲染。
看完这三种方案的简介,下面让咱们简单来作个对比吧:
React Native | Weex | Flutter | |
---|---|---|---|
平台实现 | JavaScript | JavaScript | 原生编码 |
引擎 | JS V8 | JSCore | Flutter engine |
核心语言 | React | Vue | Dart |
框架程度 | 较重 | 较轻 | 重 |
特色 | 适合开发总体 App | 适合单页面 | 适合开发总体 App |
支持 | Android、IOS | Android、IOS、Web | Android、IOS(可能还不止) |
Apk 大小(Release) | 7.6M | 10.6M | 8.1M |
小程序开发本质上仍是前端 HTML + CSS + JS 那一套逻辑,它基于 WebView 和微信(固然支付宝、百度、字节等如今都有本身的小程序,这里只是拿微信小程序作个说明)本身定义的一套 JS/WXML/WXSS/JSON 来开发和渲染页面。微信官方文档里提到,小程序运行在三端:iOS、Android 和用于调试的开发者工具,三端的脚本执行环境以及用于渲染非原生组件的环境是各不相同的。
经过更加定制化的 JSBridge,并使用双 WebView 双线程的模式隔离了 JS 逻辑与 UI 渲染,造成了特殊的开发模式,增强了 H5 与 Native 混合程度,提升了页面性能及开发体验。
Progressive Web App
, 简称 PWA,是提高 Web App 体验的一种新方法,能给用户带来原生应用的体验。
PWA 能作到原生应用的体验不是靠某一项特定的技术,而是通过应用一系列新技术进行改进,在安全、性能和体验三个方面都有了很大的提高,PWA 本质上仍是 Web App,并兼具了 Native App 的一些特性和优势,主要包括下面三点:
Android 和主流的浏览器都早已支持了 PWA 标准,在 iOS 11.3 和 macOS 10.13.4 上,苹果的 Safari 上也支持了 PWA。相信在不久的未来势必会迎来 PWA 的大爆发...
看完目前主流的混合解决方案,咱们回归本篇主题,讲解一下成熟解决方案背后的 Hybrid底层基础
,要知道决定上层建筑的永远都是底层基础,新的技术层出不穷,只有原理是不变的~~
Hybrid
,字面意思“混合”。能够简单理解为是前端和客户端的混合开发。
让咱们先来看一下目前主流的移动应用开发方式:
Native App 是一种基于智能手机本地操做系统如 iOS、Android、WP 并使用原生程式编写运行的第三方应用程序,也叫本地 app。通常使用的开发语言为 Java、C++、Objective-C。。分别来看一下 Native 开发的优缺点:
优势
缺点
Web App,顾名思义是指基于 Web 的应用,基本采用 Html5 语言写出,不须要下载安装。相似于如今所说的轻应用。基于浏览器运行的应用,基本上能够说是触屏版的网页应用。分别来看一下 Web 开发的优缺点:
优势
缺点
究其缘由就是性能要求的问题。Web app 之因此可以占领开发市场,主要是由于它的开发速度快,使用简单,应用范围广,可是在性能方面由于没法调用所有硬件底层功能,就如今讲,仍是比不过原生 App 的性能。
混合开发,也就是半原生半 Web 的开发模式,由原生提供统一的 API 给 JS 调用,实际的主要逻辑有 Html 和 JS 来完成,最终是放在 webview 中显示的,因此只须要写一套代码便可达到跨平台效果。
Hybrid App 兼具了 Native APP 用户体验佳、系统功能强大和 Web APP 跨平台、更新速度快的优点。本质实际上是在原生的 App 中,使用 WebView 做为容器直接承载 Web 页面。所以,最核心的点就是 Native 端 与 H5 端 之间的双向通信层,也就是咱们常说的 JSBridge
。
下面让咱们来看下 JS 与 Native(客户端)通讯的方式吧。
JS上下文注入
原理其实就是 Native 获取 JavaScript 环境上下文,并直接在上面挂载对象或者方法,使 JS 能够直接调用。
Android 与 IOS 分别拥有对应的挂载方式。分别对应是:苹果UIWebview JavaScriptCore注入
、安卓addJavascriptInterface注入
、苹果WKWebView scriptMessageHandler注入
。
上面这三种方式均可以被称为是JS上下文注入
,他们都有一个共同的特色就是,不经过任何拦截的办法,而是直接将一个 native 对象(or 函数)注入到 JS 里面,能够由 Web 的 JS 代码直接调用,直接操做。
弹窗拦截
这种方式主要是经过修改浏览器 Window 对象的某些方法,而后拦截固定规则的参数,以后分发给客户端对应的处理方法,从而实现通讯。
经常使用的四个方法:
简单拿 prompt 来举例说明,Web 页面经过调用 prompt()方法,安卓客户端经过监听onJsPrompt
事件,拦截传入的参数,若是参数符合必定协议规范,那么就解析参数,扔给后续的 Java 去处理。这种协议规范,最好是跟 iOS 的协议规范同样,这样跨端调起协议是一致的,但具体实现不同而已。好比:jack://utils/${action}?a=a
这样的协议,而其余格式的 prompt 参数,是不会监听的,即除了 jack://utils/${action}?a=a
这样的规范协议,prompt 仍是原来的 prompt。
但这几种方法在实际的使用中有利有弊,但因为prompt
是几个里面惟一能够自定义返回值,能够作同步交互的,因此在目前的使用中,prompt
是使用的最多的。
URL Schema
schema 是 URI 的一种格式,上文提到的jack://utils/${action}?a=a
就是一个 scheme 协议,这里说的 scheme(或者 schema)泛指安卓和 iOS 的 schema 协议,由于它比较通用。
安卓和 iOS 均可以经过拦截跳转页 URL 请求,而后解析这个 scheme 协议,符合约定规则的就给到对应的 Native 方法去处理。
安卓和 iOS 分别用于拦截 URL 请求的方法是:
shouldOverrideUrlLoading
方法delegate
函数这里简单看一个以前项目中对于 schema 封装:
// 调用 window.fsInvoke.share({title: 'xxx', content: 'xxx'}, result => { if (result.errno === 0) { alert('分享成功') } else { // 分享失败 alert(result.message) } ) ---------------------------下方为对fsInvoke的封装 (function(window, undefined) { // 分享 invokeShare = (data, callback) => { _invoke('share', data, callback) } // 登陆 invokeLogin = (data, callback) => { _invoke('login', data, callback) } // 打开扫一扫 invokeScan = (data, callback) => { _invoke('scan', data, callback) } _invoke = (action, data, callback) => { // 拼接schema协议 let schema = `jack://utils/${action}?a=a`; Object.keys(data).forEach(key => { schema += `&${key}=${data[key]}` }) // 处理callback let callbackName = ''; if(typeof callback === 'string) { callbackName = callback } else { callbackName = action + Date.now(); window[callbackName] = callback; } schema += `&callback=${callbackName}` // 触发 let iframe = document.createElement('iframe'); iframe.style.display = 'none'; iframe.src = schema; let body = document.body; body.appendChild(iframe); setTimeout(function() { body.removeChild(iframe); iframe = null; }) } // 暴露给全局 window.fsInvoke = { share: invokeShare, login: invokeLogin, scan: invokeScan } })(window)
说完了 JS 主动通知客户端(Native)的方式,下面让咱们来看下客户端(Native)主动通知调用 JS。
loadUrl
在安卓 4.4 之前是没有 evaluatingJavaScript
API 的,只能经过 loadUrl
来调用 JS 方法,只能让某个 JS 方法执行,可是没法获取该方法的返回值。这时咱们须要使用前面提到的 prompt 方法进行兼容,让 H5 端 经过 prompt 进行数据的发送,客户端进行拦截并获取数据。
// mWebView = new WebView(this); //即当前webview对象 mWebView.loadUrl("javascript: 方法名('参数,须要转为字符串')"); //ui线程中运行 runOnUiThread(new Runnable() { @Override public void run() { mWebView.loadUrl("javascript: 方法名('参数,须要转为字符串')"); Toast.makeText(Activity名.this, "调用方法...", Toast.LENGTH_SHORT).show(); } });
evaluatingJavaScript
在安卓 4.4 以后,evaluatingJavaScript 是一个很是广泛的调用方式。经过 evaluateJavascript 异步调用 JS 方法,而且能在 onReceiveValue 中拿到返回值。
//异步执行JS代码,并获取返回值 mWebView.evaluateJavascript("javascript: 方法名('参数,须要转为字符串')", new ValueCallback() { @Override public void onReceiveValue(String value) { //这里的value即为对应JS方法的返回值 } });
stringByEvaluatingJavaScriptFromString
在 iOS 中 Native 经过stringByEvaluatingJavaScriptFromString
调用 Html 绑定在 window 上的函数。
// Swift webview.stringByEvaluatingJavaScriptFromString("方法名('参数')") // oc [webView stringByEvaluatingJavaScriptFromString:@"方法名(参数);"];
看完本篇文章,相信你对 Hybrid 有了一个初步的了解。虽然本篇比较基础,可是只有了解了最本质的底层原理后,才能对现有的解决方案有一个很好的理解,你也能够去打造适合你和团队的Hybrid方案
。固然了,后面会有对于 Hybrid 更深刻的探讨,敬请期待哦!!
你能够关注个人同名公众号【前端森林】,这里我会按期发一些大前端相关的前沿文章和平常开发过程当中的实战总结。固然,我也是开源社区的积极贡献者,github地址https://github.com/Jack-cool,欢迎star!!!