大前端跨平台技术


在移动互联网时代,任何一个公司的产品都少不了APP,这是人们生活水平提高的结果。随着智能手机越来越普及,移动端成为兵家必争之地。正所谓“得移动端者得天下”,移动端已成为互联网领域最大的流量分发入口,一大批互联网公司正是在这大趋势下崛起。但在移动端刚出来那会儿,Android、iOS都是各自为营,分开开发的,团队之间是独立的,从需求调研,研发,测试,上线一整套流程需要的周期很长,少则几个月,多达1年甚至更长。这样的项目周期满足不了高速迭代发展的需求,伴随着移动互联网的高速发展,公司间竞争越来越激烈,如何将好想法快速落地、快速试错,成为备受关注的问题。因为只有尽快占领市场,才能在移动互联网中分得一杯羹。在这种场景下,跨平台框架应运而生

众所周知,Android 应用采用 Java 或 Kotlin 编写,iOS 应用采用 Objective-C 或 Swift 编写,Web 端采用 HTML /CSS/JavaScript 编写。当需要开发支持多端的应用,每一端都需要独立研发、测试,一直到上线,以及后续的维护工作,工作量成倍增涨,势必延长研发周期。因此跨平台框架的出现是为了解决多端独立开发,开发维护成本高,项目周期长的问题。

几种APP开发模式

主流应用程序大体分为四大类型:Native App、Hybrid App 、Web App,随着大前端的流行又衍生出很多跨平台解决方案和框架。什么是大前端:大前端是指移动端(主要是安卓和IOS),PC端(主要是网页web),小程序,也包括车载终端等客户端。随着终端越来越多于是出现了跨平台应用,跨平台应用就是实现一套前端代码,可以在PC端,IOS端,安卓端,还有其他终端(如果车载终端等)完美运行的应用,人们称之为跨平台应用。

Native App

只用平台原生的开发语言,开发框架来做开发APP。是一种基于智能手机本地操作系统如iOS、Android、WP并使用原生程式编写运行的应用程序。由于是原生开发,性能比较好可以直接调用摄像头,蓝牙等硬件。 这种原生程序一般依托于操作系统,有很强的交互,是一个完整的App,可拓展性强。缺点是对于不同的操作系统需要使用特定的语言开发,不具备移植性,IOS系统开发语言:Objective-C / Swift。Android系统 开发语言:Java / Kotlin

优点

  1. 直接依托于操作系统,交互性最强,性能最好,体验是最优。
  2. 功能最为强大,特别是在与系统交互中,几乎所有功能都能实现

缺点

  1. 开发成本高,无法跨平台,不同平台Android和iOS上都要各自独立开发
  2. 门槛较高,原生人员有一定的入门门槛。相比广大的前端人员而言,较少
  3. 更新缓慢,特别是发布应用商店后,需要等到审核周期。原生应用更新是一个很大的问题,Android中还能直接下载整包APK进行更新,但在IOS中,如果是发布AppStore,必须通过AppStore地址更新,而每次更新都需要审核,所以无法达到及时更新
  4. 维护成本高

Hybrid App

用 JS+H5 开发应用的技术,可以通过某种手段调用系统能力。从原生开发者的角度,混合应用其实就是一个原生开发的 App 外壳,这个外壳将原生功能封装成很多 API 并注入到 WebView 里,然后将前端页面打包进 App,App 启动时用 WebView 加载前端页面,剩下的就全交给前端了。混合应用框架的本质就是这个原生 App 外壳,这个外壳重点实现三件事,只要做到这三件事,基本上就可以被称为混合开发通用框架。

  1. 实现原生与前端(Javascript)的交互
  2. 封装基本的原生功能,供前端调用
  3. 实现原生插件机制,供原生开发者扩展功能

简而言之,在Hybrid模式下,由原生提供统一的API给JS调用,实际的主要逻辑有Html和JS来完成,而由于最终是放在webview中显示的,所以只需要写一套代码即可。

优点

  1. 开发成本较低,可以跨平台,调试方便,前端人员稍微学习下JS api的调用即可,无需两个独立的原生人员。一般Hybrid中的跨平台最少可以跨三个平台:Android App,iOS App,普通webkit浏览器
  2. 维护成本低,功能可复用,如果代码合理,只需要一名前端就可以维护多个app,而且很多功能还可以互相复用
  3. 更新较为自由,虽然没有Web App更新那么快速,但是Hybrid中也可以通过原生提供api,进行资源主动下载,达到只更新资源文件,不更新apk(ipa)的效果
  4. 针对新手友好,学习成本较低。这种开发模式下,只需要前端人员关注一些原生提供的API,具体的实现无需关心,没有新的学习内容,只需要前端人员即可开发
  5. 功能更加完善,性能和体验要比起web app好太多。因为可以调用原生api,所以很多功能只要原生提供出就可以实现,另外性能也比较接近原生了
  6. 部分性能要求的页面可用原生实现。这应该是Hybrid模式的最多一个好处了,因为这种模式是原生混合web,所以我们完全可以将交互强,性能要求高的页面用原生写,然后一些其它页面用JS写,嵌入webview中以达到最佳体验

缺点

  1. 相比原生,性能仍然有较大损耗。这种模式受限于webview的性能桎梏,相比原生而言有不少损耗,体验无法和原生相比。不过相比于原生的诸多优点,这个缺点显然可以接受
  2. 不适用于交互性较强的App,这种模式的主要使用一些新闻阅读类,信息展示类的App。但是不适用于一些交互较强或者性能要求较高的App

Web App

指采用H5语言写出的App,不需要下载安装。类似于现在所说的轻应用,生存在浏览器中的应用,可以说是触屏版的网页应用。类比于电脑网页中的应用,只是移动端有触屏和手势。Web App生存于浏览器里,宿主是浏览器。对比与原生应用缺点也很明显,因为web App实际上就是手机上的网页,依赖于浏览器内核,无法调用系统权限如地理信息,通讯录,语音,摄像头等等。而且由于不同的浏览器身的属性不尽相同,如浏览器自带的手势,页面切换方式,链接跳转方式,存在版本兼容问题等等

优点

  1. 开发成本低,可以跨平台,调试方便。Web App一般只需要一个前端人员开发出一套代码,然后即可应用于各大主流浏览器(特殊情况可以代码进行下兼容)。没有新的学习成本,而且可以直接在浏览器中调试。
  2. 维护成本低
  3. 更新最为快速,由于web app资源是直接部署在服务器端的,所以只需要替换服务器端的文件,用户访问是就已经更新了(当然需要解决一些缓存问题)
  4. 无需安装App,不会占用手机内存,通过浏览器即可访问,无需安装,用户就会比较愿意去用

缺点

  1. 性能低,用户体验差,由于是直接通过的浏览器访问,所以无法使用原生的API,操作体验不好。而且由于依赖于浏览器,部分手势还可能和浏览器的手势冲突
  2. 依赖于网络,页面访问速度慢,耗费流量。Web App每次访问都需要去服务端加载资源访问,所以必须依赖于网络。而且网速慢时访问速度很不理想,特别是在移动端,如果网站优化不好会无故消耗大量流量
  3. 功能受限,大量功能无法实现。只能使用Html5的一些特殊api,无法调用原生API,所以很多功能存在无法实现情况
  4. 临时性入口,用户留存率低。这既是它的优点,也是缺点,优点是无需安装。确定是用完后有时候很难再找到,或者说很难专门为某个web app留存一个入口,导致用户很难再次使用

React Native App

Facebook发起的开源的一套新的APP开发方案,Facebook在当初深入研究Hybrid开发后,觉得这种模式有先天的缺陷,所以果断放弃,转而自行研究,后来推出了自己的“React Native”方案,不同于H5,也不同于原生,更像是用JS写出原生应用。用 javascript语言就能同时编写 ios,android,以及后台的一项技术。用React Native 就是真正意义上的全栈,一个项目从头到尾可以一个人搞定。一开始我以为React Native App属于Hybrid App。因为它们都使用JS写应用,只不过Hybrid App用WebView 实现,而React Native App自己写了一个引擎实现。但其实不是,在开发的角度来说,两者使用起来差不太多,都可以用JS写App,但是React Native App编译之后生成的是原生的控件,所以是原生应用。但开发模式上与原生App差别又很大,应该说是一种跨平台解决方案的新思路,其实很多大公司都已经在使用React Native开发了

优点

  1. 开发人员单一技术栈,一次学习,跨平台开发。这种模式是统一由JS编写,有着独特的语法,所以只需要学习一次,即可同时开发Android和iOS
  2. 相对Hybird app或者Webapp。不用Webview,彻底摆脱了Webview让人不爽的交互和性能问题,有较强的扩展性,这是因为Native端提供的是基本控件,JS可以自由组合使用可以直接使用Native原生的动画(在FB Group这个app里面,面板滑出带一点果冻弹动,面板基于某个点展开这种动画随处可见,这种动画用Native code来做小菜一碟,但是用Web来做就难上加难)。
  3. 相对于Native App。可以通过更新远端JS,直接更新app,不过这快成为各家大型Native app的标配了
  4. 社区繁荣,遇到问题容易解决。这应该是React Native的很大一个优势,不像Hybrid模式和原生模式一样各自为营,这种模式是Facebook统一发起的,所以有一个统一的社区,里面有大量资源和活跃的人员,对开发者很友好
  5. 性能体验高于Hybrid,不逊色与原生。这种模式和Hybrid不一样,Hybrid中的view层实际上还是dom,但是这种模式的view层是虚拟dom,所以性能要高于Hybrid,距离原生差距不大。这种模式可以认为是用JS写原生,即页面用JS写,然后原生通过Bridge技术分析JS,将JS内容单独渲染成原生Android和iOS,所以也就是为什么性能不逊色原生

缺点

  1. 从Native到Web,要做很多概念转换,势必造成双方都要妥协。最终web要用一套CSS的阉割版,Native要费劲地把这个阉割版转换成native原生的表达方式(比如iOS的Constraint\origin\Center等属性),两边都会不爽
  2. 虽然可以部分跨平台,但并不是Hybrid中的一次编写,两次运行那种,而是不同平台代码有所区别。这种模式实际上还是JS来写原生,所以Android和iOS中的原生代码会有所区别,如果需要跨平台,对开发人员有一定要求。

WebView简介

为了理解Hybrid App与React Native App的区别,你不得不知道的组件就是WebView。Android WebView在Android平台上是一个特殊的View, 基于webkit引擎、展现web页面的控件,这个类可以被用来在你的app中仅仅显示一张在线的网页,还可以用来开发浏览器。WebView内部实现是采用渲染引擎来展示view的内容,提供网页前进后退,网页放大,缩小,搜索。现在很多App里都内置了Web网页(Hybrid App),比如说很多电商平台,淘宝、京东、聚划算等等,如下图

京东首页

Android的Webview在低版本和高版本采用了不同的webkit版本内核,4.4后直接使用了Chrome。WebView控件功能强大,除了具有一般View的属性和设置外,还可以对url请求、页面加载、渲染、页面交互进行强大的处理。

Webview类常用方法

加载url,加载方式根据资源分为三种,如下


  //方式1. 加载一个网页:
  webView.loadUrl("http://www.google.com/");

  //方式2:加载apk包中的html页面
  webView.loadUrl("file:///android_asset/test.html");

  //方式3:加载手机本地的html页面
   webView.loadUrl("content://com.android.htmlfileprovider/sdcard/test.html");

   // 方式4: 加载 HTML 页面的一小段内容
  WebView.loadData(String data, String mimeType, String encoding)
// 参数说明:
// 参数1:需要截取展示的内容
// 内容里不能出现 ’#’, ‘%’, ‘\’ , ‘?’ 这四个字符,若出现了需用 %23, %25, %27, %3f 对应来替代,否则会出现异常
// 参数2:展示内容的类型
// 参数3:字节码

WebView的状态

//激活WebView为活跃状态,能正常执行网页的响应
webView.onResume()//当页面被失去焦点被切换到后台不可见状态,需要执行onPause
//通过onPause动作通知内核暂停所有的动作,比如DOM的解析、plugin的执行、JavaScript执行。
webView.onPause()//当应用程序(存在webview)被切换到后台时,这个方法不仅仅针对当前的webview而是全局的全应用程序的webview
//它会暂停所有webview的layout,parsing,javascripttimer。降低CPU功耗。
webView.pauseTimers()
//恢复pauseTimers状态
webView.resumeTimers()//销毁Webview
//在关闭了Activity时,如果Webview的音乐或视频,还在播放。就必须销毁Webview
//但是注意:webview调用destory时,webview仍绑定在Activity上
//这是由于自定义webview构建时传入了该Activity的context对象
//因此需要先从父容器中移除webview,然后再销毁webview:
rootLayout.removeView(webView); 
webView.destroy();

Webview与 Js的交互

Webview既然能够嵌套HTML页面,而HTML中最重要的就是JS事件了,那么Webview与JS事件的交互就是开发中经常需要面对的问题,这里介绍一种比较通用的方式,通过 WebChromeClientonJsAlert()onJsConfirm()onJsPrompt()方法回调拦截JS对话框alert()confirm()prompt() 消息。

javascript.html:以.html格式放到src/main/assets文件夹里

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>Carson_Ho</title>

     <script>

    function clickprompt(){
    // 调用prompt()
    var result=prompt("js://demo?arg1=111&arg2=222");
    alert("demo " + result);
}

      </script>
</head>

<!-- 点击按钮则调用clickprompt()  -->
   <body>
     <button type="button" id="button1" onclick="clickprompt()">点击调用Android代码</button>
   </body>
</html>

当使用mWebView.loadUrl("file:///android_asset/javascript.html")加载了上述JS代码后,就会触发回调onJsPrompt(),具体如下:

  1. 如果是拦截警告框(即alert()),则触发回调onJsAlert()
  2. 如果是拦截确认框(即confirm()),则触发回调onJsConfirm()

Webview:在Android通过WebChromeClient复写onJsPrompt()

public class MainActivity extends AppCompatActivity {

    WebView mWebView;
//    Button button;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mWebView = (WebView) findViewById(R.id.webview);

        WebSettings webSettings = mWebView.getSettings();

        // 设置与Js交互的权限
        webSettings.setJavaScriptEnabled(true);
        // 设置允许JS弹窗
        webSettings.setJavaScriptCanOpenWindowsAutomatically(true);

// 先加载JS代码
        // 格式规定为:file:///android_asset/文件名.html
        mWebView.loadUrl("file:///android_asset/javascript.html");


        mWebView.setWebChromeClient(new WebChromeClient() {
                                        // 拦截输入框(原理同方式2)
                                        // 参数message:代表promt()的内容(不是url)
                                        // 参数result:代表输入框的返回值
                                        @Override
                                        public boolean onJsPrompt(WebView view, String url, String message, String defaultValue, JsPromptResult result) {
                                            // 根据协议的参数,判断是否是所需要的url(原理同方式2)
                                            // 一般根据scheme(协议格式) & authority(协议名)判断(前两个参数)
                                            //假定传入进来的 url = "js://webview?arg1=111&arg2=222"(同时也是约定好的需要拦截的)

                                            Uri uri = Uri.parse(message);
                                            // 如果url的协议 = 预先约定的 js 协议
                                            // 就解析往下解析参数
                                            if ( uri.getScheme().equals("js")) {

                                                // 如果 authority  = 预先约定协议里的 webview,即代表都符合约定的协议
                                                // 所以拦截url,下面JS开始调用Android需要的方法
                                                if (uri.getAuthority().equals("webview")) {

                                                    //
                                                    // 执行JS所需要调用的逻辑
                                                    System.out.println("js调用了Android的方法");
                                                    // 可以在协议上带有参数并传递到Android上
                                                    HashMap<String, String> params = new HashMap<>();
                                                    Set<String> collection = uri.getQueryParameterNames();

                                                    //参数result:代表消息框的返回值(输入值)
                                                    result.confirm("js调用了Android的方法成功啦");
                                                }
                                                return true;
                                            }
                                            return super.onJsPrompt(view, url, message, defaultValue, result);
                                        }

// 通过alert()和confirm()拦截的原理相同,此处不作过多讲述

                                        // 拦截JS的警告框
                                        @Override
                                        public boolean onJsAlert(WebView view, String url, String message, JsResult result) {
                                            return super.onJsAlert(view, url, message, result);
                                        }

                                        // 拦截JS的确认框
                                        @Override
                                        public boolean onJsConfirm(WebView view, String url, String message, JsResult result) {
                                            return super.onJsConfirm(view, url, message, result);
                                        }
                                    }
        );


            }

        }

最终结果展示

通过刚刚的代码,对Webview的作用应该理解了,借此对刚刚说的几种App类型用大白话做一个总结吧。

App 解释
Native App 原生应用。类比于windows系统上的桌面应用,可以轻松的调用系统提供的API
但移植性差,例如windows上的桌面应用不能直接在Linux上使用
Web App Web 应用。严格来说,我觉得它不能称之为App,它的实现类比于windows上的网页
其实就可以理解为它是移动端的网页应用。而我理解的App是安装在操作系统上的App
Hybrid App 混合应用。它实际上可以说是原生组件提供的功能,在系统原生组件中提供了WebView组件
这个组件特殊的地方在于,可以在这个组件中展示HTML页面。因此很多人说
现在的App开发就是用H5开发,然后套了个外壳,这个壳指的就是WebView
React Native App 原生应用。虽然开发的时候是用JS开发,但是编译后是原生组件。可以这么理解
React Native框架提供了React组件到原生组件的映射,每个JS事件可以对应到原生组件的事件
最终编译后把React组件和对应的JS事件代码翻译成原生组件和事件代码

以上是个人理解。可能会有类比不恰当的地方,但大概就是这么个意思吧

APP对比

Native App Hybrid App Web App React Native App
App特性比较
图像渲染 本地API渲染 混合 H5 本地API渲染
性能 最快
界面 原生 模仿 模仿
发布 App Store App Store Web App Store
本机设备访问
照相机 支持 支持 不支持 支持
系统通知 支持 支持 不支持 支持
定位 支持 支持 支持 支持
蓝牙 支持 支持 不支持 支持
其他比较
开发成本
维护更新 复杂 简单 简单 简单
体验
Store或Market 认可 认可 不认可 认可
安装 需要 需要 不需要 需要
跨平台
网络要求 支持离线 依赖网络 依赖网络 支持离线
资源存储 本地 本地和服务器 服务器 本地
适用场景 1.偏操作互动多的工具类应用
2.需要访问特定的原生API
3.对速度要求较高
1.同Native App中提到的适用场景
2.需要频繁小幅度更新
1.作为对非核心业务在移动端的入口补足
2.作为用户轻量、低频使用的体验增强
3.作为吸引用户安装Native App的引导页
1.适用于快速迭代不同平台的App应用
2.绝大部分功能在不同系统上可以使用一套代码解决,成本低

其他类似React Native App框架

React Native App的实现原理最主要的就是在编译时,把JS组件转换为原生组件。市面上还有一些实现了这种转换功能的跨平台框架。

Weex

Weex 是阿里开源的一款跨平台移动开发工具,是阿里巴巴开发团队在RN的成功案例上,重新设计出的一套开发模式,站在了巨人肩膀上并有淘宝团队项目做养料,广受关注,2016年4月正式开源,并在v2.0版本官方支持Vue.js,与RN分庭抗礼。Weex 这个名字是取得 weeks 的谐音。Weex能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持iOS、安卓、YunOS及Web等多端部署。Weex 是一个使用 Web 开发体验来开发高性能原生应用的框架。使用同一套代码就可以构建 Android、iOS 和 Web 应用。Weex 的结构是解耦的,渲染引擎与语法层是分开的,目前主要支持 Vue.js 和 Rax 这两个前端框架。Weex 在 iOS 和 Android 上都实现了一个渲染引擎,并提供了一套基础的内置组件。基于这些组件,你可以用JS封装更多的上层组件。

Weex于2016年6月开始发布版本,第一个版本号为v0.5.0。

Flutter

Flutter是谷歌推出的跨平台项目,它的前身是Sky项目,起源于2015年。Sky项目一开始就定位Dart作为开发语言,使用Dart语言开发移动端项目,Sky它不依赖于平台,它的代码可以运行在Android、iOS设备上,真正做到了“一次代码,处处运行”,让你在Android、iOS设备上拥有接近原生的体验。主要特点是跨平台、高保真、有些性能。Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 Native扩展。同时Flutter还可以使用 Native引擎渲染视图,这无疑能为用户提供良好的体验。

Flutter在2017年5月发布了第一个版本v0.0.6。

其他跨平台框架

市面有很多实现了跨平台的框架,其实现原理基本属于上面四大类App中的一种,下面分别介绍一下它们

Taro

小程序跨平台开发,一款可以用TSX、JSX和React语法开发小程序的框架,内置了一些UI组件,还有物料市场,目前看势头很好。还可以集成React-native,真正做到一套代码多处运行,不仅能编译成各种平台小程序,还可以是RN的应用,还支持快应用。官网地址点这里。现如今市面上端的形态多种多样,Web、React-Native、微信小程序等各种端大行其道,当业务要求同时在不同的端都要求有所表现的时候,针对不同的端去编写多套代码的成本显然非常高,这时候只编写一套代码就能够适配到多端的能力就显得极为需要。

使用 Taro,我们可以只书写一套代码,再通过 Taro 的编译工具,将源代码分别编译出可以在不同端(微信/百度/支付宝/字节跳动/QQ/京东小程序、快应用、H5、React-Native 等)运行的代码。

Taro的源码我没看过,但是我看里面用了很多他们自己写的babel包,应该是JIT模式,加入了中间层,把你写的东西,编译成了小程序可以执行的代码,个人认为小程序不要做得太复杂,不然你还不如做个APP,轻量跨平台,自然是最快速的,而且可以使用TSX语法,React,太好了。

快应用

华为、小米等九大手机厂商为了跟小程序竞争搞出来的,基于硬件平台共同推出的新型应用生态。像RN这些框架,会内置一些渲染/排版引擎,那么打包出来提交比较大,快应用是集成到安卓手机的ROM中,所以只有源码那部分,安装体积比较小,这样就叫快应用。快应用使用原生js开发,框架跟原生微信小程序很像(写着不舒服,Taro支持快应用),官网地址点这里

Electron

Electron就是把Node.js的运行环境和谷歌浏览器内核一起打包了,于是就拥有了Node.js和H5技术的融合能力,又因为是基于C++编写,于是可以跨平台。Mac、windows、Linux。所以Electron是一个用 HTMLCSSJavaScript 来构建跨平台桌面应用程序的一个开源库,没错,你可以用写网页的知识来写一个桌面应用程序!Electron开发出来的东西是软件,是一个安装在电脑上的软件!是不是发现了新大陆,前端开发不仅仅只能写web页面了,可以做的事情很多了。官网地址点这里


Author: 顺坚
Reprint policy: All articles in this blog are used except for special statements CC BY 4.0 reprint polocy. If reproduced, please indicate source 顺坚 !
评论
 Previous
JavaScript中的this指向 JavaScript中的this指向
this 是JavaScript中很重要的一个指针,但是往往也是最容易产生bug 的地方,因为稍不留神this的指向就和你以为的指向根本不是一个对象,让多数新手懵逼,部分老手觉得恶心,这是因为this的绑定 [难以捉摸],出错的时候还往往不
2020-05-26
Next 
React使用总结 React使用总结
使用了一段时间React,从最开始的排斥到习惯,然后再到逐渐喜欢上它,在此做一个总结。我在之前使用过一段时间的Vue,并通过Vue把公司的风控项目重构成了一个前后端分离项目。由于参与另一个项目便开始使用React。说实话,在使用Vue后再使
2020-05-17
  TOC