浅谈Hybrid

引言

随着 Web 技术和移动设备的飞速发展,各类 APP 层出不穷,极速的业务扩展提升了团队对开发效率的要求,这个时候使用 IOS/Andriod 开发一个 APP 彷佛成本有点太高了,而 H5 的低成本、高效率、跨平台等特性立刻被利用起来造成了一种新的开发模式:Hybrid APPjavascript

Hybrid 技术已经成为一种最主流最多见的方案。一套好的 Hybrid 架构解决方案能让 App 既能拥有极致的体验和性能,同时也能拥有 Web 技术 灵活的开发模式、跨平台能力以及热更新机制。本文主要是结合我最近开发的一个 Hybrid 项目(https://github.com/Jack-cool/hybrid_jd),带你们全面了解一下 Hybrid。前端

现有混合方案

深刻了解 Hybrid 前,让咱们先来看一下目前市面上比较成熟的混合解决方案。java

基于 WebView UI 的基础方案

这种是市面上大多数 app 采起的方案,也是混合开发最基础的方案。在 webview 的基础上,与原生客户端创建js bridge桥接,以达到 js 调用Native API和 Native 执行js方法的目的。react

目前国内绝大部分的大厂都有一套本身的基于 webview ui 的 hybrid 解决方案,例如微信的JS-SDK,支付宝的JSAPI等,经过JSBridge完成 h5 与 Native 的双向通信,从而赋予 H5 必定程度的原生能力。android

基于 Native UI 的方案

能够简单理解为“跨平台”,如今比较通用的有React NativeWeexFlutter等。在赋予 H5 原生 API 能力的基础上,进一步经过 JSBridge 将 JS 解析成的虚拟节点数(Virtual DOM)传递到 Native 并使用原生渲染。咱们这里来看下上面提到的这三种:ios

React Native

“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

Weex

“Write once, run everywhere”,基于 Vue 设计模式,支持 web、android、ios 三端,原生端一样经过中间层转化,将控件和操做转化为原生逻辑来提高用户体验。编程

在 weex 中,主要包括三大部分:JS BridgeRenderDom,JS Bridge 主要用来和 JS 端实现进行双向通讯,好比把 JS 端的 dom 结构传递给 Dom 线程。Dom 主要是用于负责 dom 的解析、映射、添加等等的操做,最后通知 UI 线程更新。而 Render 负责在 UI 线程中对 dom 实现渲染。

和 react native 同样,weex 全部的标签也都不是真实控件,JS 代码中所生成的 dom,最终都是由 Native 端解析,再获得对应的 Native 控件渲染,如 Android 中 标签对应 WXTextView 控件。

Flutter

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 混合程度,提升了页面性能及开发体验。

PWA

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 是什么,为何要用 Hybrid?

Hybrid,字面意思“混合”。能够简单理解为是前端和客户端的混合开发。

让咱们先来看一下目前主流的移动应用开发方式:

Native APP

Native App 是一种基于智能手机本地操做系统如 iOS、Android、WP 并使用原生程式编写运行的第三方应用程序,也叫本地 app。通常使用的开发语言为 Java、C++、Objective-C。。分别来看一下 Native 开发的优缺点:

  • 优势

    • 用户体验近乎完美
    • 性能稳定
    • 访问本地资源(通信录、相册)
    • 操做流畅
    • 设计出色的动效、转场
    • 系统级的贴心通知或提醒
    • 用户留存率高
  • 缺点

    • 门槛高,原生开发人才稀缺,至少比前端和后端少,开发环境昂贵
    • 发布成本高,须要经过 store 或 market 的审核,致使更新缓慢
    • 维持多个版本、多个系统的成本比较高,并且必须作兼容
    • 没法跨平台,开发的成本比较大,各个系统独立开发

Web APP

Web App,顾名思义是指基于 Web 的应用,基本采用 Html5 语言写出,不须要下载安装。相似于如今所说的轻应用。基于浏览器运行的应用,基本上能够说是触屏版的网页应用。分别来看一下 Web 开发的优缺点:

  • 优势

    • 开发成本低
    • 临时入口,能够随意嵌入
    • 无需安装,不会占用手机内存,并且更新速度最快
    • 可以跨多个平台和终端
    • 不存在多版本问题,维护成本低
  • 缺点

    • 没法获取系统级别的通知,提醒,动效等等
    • 设计受限制较多
    • 体验较差
    • 受限于手机和浏览器性能,用户体验相较于其余模式最差
    • 用户留存率低

究其缘由就是性能要求的问题。Web app 之因此可以占领开发市场,主要是由于它的开发速度快,使用简单,应用范围广,可是在性能方面由于没法调用所有硬件底层功能,就如今讲,仍是比不过原生 App 的性能。

Hybrid APP

混合开发,也就是半原生半 Web 的开发模式,由原生提供统一的 API 给 JS 调用,实际的主要逻辑有 Html 和 JS 来完成,最终是放在 webview 中显示的,因此只须要写一套代码便可达到跨平台效果。

Hybrid App 兼具了 Native APP 用户体验佳、系统功能强大和 Web APP 跨平台、更新速度快的优点。本质实际上是在原生的 App 中,使用 WebView 做为容器直接承载 Web 页面。所以,最核心的点就是 Native 端 与 H5 端 之间的双向通信层,也就是咱们常说的 JSBridge

下面让咱们来看下 JS 与 Native(客户端)通讯的方式吧。

JS 与客户端通讯

JS 通知客户端(Native)

JS上下文注入

原理其实就是 Native 获取 JavaScript 环境上下文,并直接在上面挂载对象或者方法,使 JS 能够直接调用。

Android 与 IOS 分别拥有对应的挂载方式。分别对应是:苹果UIWebview JavaScriptCore注入安卓addJavascriptInterface注入苹果WKWebView scriptMessageHandler注入

上面这三种方式均可以被称为是JS上下文注入,他们都有一个共同的特色就是,不经过任何拦截的办法,而是直接将一个 native 对象(or 函数)注入到 JS 里面,能够由 Web 的 JS 代码直接调用,直接操做。

弹窗拦截

这种方式主要是经过修改浏览器 Window 对象的某些方法,而后拦截固定规则的参数,以后分发给客户端对应的处理方法,从而实现通讯。

经常使用的四个方法:

  • alert: 能够被 webview 的 onJsAlert 监听
  • confirm: 能够被 webview 的 onJsConfirm 监听
  • prompt: 能够被 webview 的 onJsPrompt 监听

简单拿 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 请求的方法是:

  • android:shouldOverrideUrlLoading方法
  • iOS:UIWebView 的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。

客户端(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!!!

相关文章
相关标签/搜索