注:本文为转载文章,部分内容参考​​移动端跨平台开发的深度解析​​,并做了精简和加工。

概述

移动跨平台开发一直是移动开发者和前端开发者追求的的话题,从早期的cordova、ionic,到如今的react native、weex、kotlin native和flutter等,可以说如今的跨平台框架可谓百花齐放,颇有一股推倒原生开发者的势头。

如果要对目前的跨平台的方案进行一个总结,大致可以分为以下几个流派:
JavaScript流派:这一流派中,最明显的特征是使用JavaScript作为编程语言,react native、weex均属于这一流派。和其他跨平台方案相比,JavaScript在跨平台开发中,使用者最多,大有“一统天下”的趋势。
VM虚拟机:与其他方案不同,kotlin提供的kotlin-native技术拥有自己的VM,可以同时支持Android、iOS 和 Web 开发。
Flutter:Futter是Google开源的移动跨平台UI框架,使用的是Google自己的Dart编程语言,由于是Google推出的产品,因而也受到很多开发者的喜爱。

不过,综合对比开发现,目前最火的跨平台开发方案,特别是已经商用的跨平台开发框架中,react native无疑是老大,其次是Weex(毕竟是阿里的产品),最后可能是最近才火起来的Flutter。

移动跨平台开发深度解析_flutter

React Native

曾经,React Native的口号是“Learn once, write anywhere”,这句话代表了FaceBook对React Native设计的初衷:学习 react ,同时掌握 web 与 app 两种开发技能。

借助FaceBook旗下的React的设计模式 , React Native使用的UI渲染、动画效果、网络请求等会转换成原生端的实现。也就是说,开发者编写的js代码,通过 react native 的中间层(JavaScriptCore)转化为原生控件和操作,这就最大程度的接近原生应用的用户体验,并提高了开发的效率。

React Native的结构

React Native的跨平台是实现主要由三层构成,其中 C++ 实现的动态连结库(.so),作为中间适配层桥接,实现了js端与原生端的双向通信交互。

这里最主要是封装了 JavaScriptCore 执行js的解析,而 react native 运行在JavaScriptCore中,所以不存在浏览器兼容的问题。其结构如如下图:

移动跨平台开发深度解析_bundle_02

原理

React Native实现的原理其实就是利用JS 调用Native 端的组件,并使用Native的组件来绘制界面,从而达到媲美原生应用的效果。

和前端开发不同,React Native 所使用的标签并不是真实的控件,React Native提供的组件会Dom 转换为Native的控件进行渲染。例如​​<Text>​​ 标签会被转换为Android 中对应 TextView 控件。

移动跨平台开发深度解析_flutter_03

需要说明的是,在React Native 中,JS端是运行在独立的线程中(称为JS Thread ),JS Thread 作为单线程逻辑,不可能处理耗时的操作。那么如 fetch 、图片加载 、 数据持久化等操作,在 Android 中实际对应的是 okhttp 、Fresco 、SharedPreferences等。而跨线程通信,也意味着 Js Thread 和原生之间交互与通讯是异步的。

由此可以看出,跨平台的关键在于C++层,开发人员大部分时候,只专注于JS 端的代码实现即可,无线了解底层的实现细节。 而如果要实现和原生模块的交互,只需要在原生端提供的各种 Native Module 模块(如网络请求,ViewGroup控件)即可,然后通过 JS 端提供的各种 JS Module(如JS EventEmiter模块)来实现调用。并且这些调用都会在C++实现的so中保存起来,双方的通讯通过C++中的保存的映射,最终实现两端的交互,通信的数据和指令,在中间层会被转为String字符串传输,双向的调用流程如下图。

移动跨平台开发深度解析_bundle_04

打包与发布

在React Native混合项目中,JS代码会被打包成一个 bundle 文件,自动添加到 App 的资源目录下。react native 的打包脚本目录为/node_modules/react-native/local-cli,打包最后会通过 metro 模块压缩 bundle 文件。而bundle文件只会打包js代码,自然不会包含图片等静态资源,所以打包后的静态资源,其实是被拷贝到对应的平台资源文件夹中。

举个例子,react native 项目会将图片存储在根目录下的 img/pic/logo.png 的资源,编译时,会被重命名后,根据大小 merged 到对应的是drawable目录下,修改名称为img_pic_logo.png。

Weex

Weex是阿里巴巴开源的一套移动跨平台开发框架,能够完美兼顾性能与动态性,让移动开发者通过简捷的前端语法写出Native级别的性能体验,并支持iOS、安卓、YunOS及Web等多端部署。目前,使用Weex框架的多半是阿里系的产品和一些创业的公司。

Weex架构

Weex的口号是“Write once, run everywhere”,Weex使用的耳熟能详的Vue,阿里的思维是:写个 vue 前端,顺便完成一个apk 和 ipa,但其实还是有差距的。Weex支持 web、android、ios 三端,原生端同样通过中间层转化,将控件和操作转化为原生逻辑来提高用户体验。。

在 Weex的架构层次中,主要包括三大部分:JS Bridge、Render、Dom,分别对应WXBridgeManager、WXRenderManager、WXDomManager,三部分通过WXSDKManager统一管理。其中 JS Bridge 和 Dom 都运行在独立的 HandlerThread 中,而 Render 运行在 UI 线程。关于Weex架构分层的内容读者可以自行查看官方资料的介绍。

其中,JS Bridge 主要用来和 JS 端实现进行双向通信,比如把 JS 端的 dom 结构传递给 Dom 线程。Dom 主要是用于负责 dom 的解析、映射、添加等等的操作,最后通知UI线程更新。而 Render 负责在UI线程中对 dom 实现渲染。

移动跨平台开发深度解析_flutter_05

实现原理

和 React Native一样,Weex 所有的标签也不是真实控件,Weex的标签只不过是JS 代码中所生成存的 dom,最后都是由 Native 端解析,再得到对应的Native控件渲染。如 Android 中 ​​<text>​​ 标签对应 WXTextView 控件。

Weex 中文件默认为 .vue ,而 vue 文件是被无法直接运行的,所以 vue 会被编译成 .js 格式的文件,Weex SDK会负责加载渲染这个js文件。Weex可以做到跨三端的原理在于:在开发过程中,代码模式、编译过程、模板组件、数据绑定、生命周期等上层语法是一致的。

不同的是,在 JS Framework 层的最后,web 平台和 Native 平台,对 Virtual DOM 执行的解析方法是有区别的,在渲染真实 UI 的时候调用的接口也不同的。

移动跨平台开发深度解析_flutter_06


Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发、云端部署到分发的整个链路。开发者首先可在本地像编写 web 页面一样编写一个 app 的界面,然后通过命令行工具将之编译成一段 JavaScript 代码,生成一个 Weex 的 JS bundle;同时,开发者可以将生成的 JS bundle 部署至云端,然后通过网络请求或预下发的方式加载至用户的移动应用客户端;在移动应用客户端里,Weex SDK 会准备好一个 JavaScript 执行环境,并且在用户打开一个 Weex 页面时在这个执行环境中执行相应的 JS bundle,并将执行过程中产生的各种命令发送到 native 端进行界面渲染、数据存储、网络通信、调用设备功能及用户交互响应等功能;同时,如果用户希望使用浏览器访问这个界面,那么他可以在浏览器里打开一个相同的 web 页面,这个页面和移动应用使用相同的页面源代码,但被编译成适合Web展示的JS Bundle,通过浏览器里的 JavaScript 引擎及 Weex SDK 运行起来的。

移动跨平台开发深度解析_bundle_07


其中, Native 加载bundle 文件大致经历了以下阶段:

  • weex 接收到 js 文件以后,JS Framework 根据文件为 Vue 模式,会调用weex-vue-framework 中提供的createInstance方法创建实例。
  • createInstance 中会执行 Js Entry 代码里 new Vue() 创建一个组件,通过其 render 函数创建出 Virtual DOM 节点。
  • 由JS V8 引擎上解析 Virtual DOM ,得到 Json 数据发送至 Dom 线,这里输出 Json 也是方便跨端的数据传输。
  • Dom 线程解析 Json 数据,得到对应的 WxDomObject,然后创建对应的WxComponent 提交 Render 。
  • Render在原生端最终处理处理渲染任务,并回调里JS方法。

相比React Native,Weex主要是在JS V8的引擎上多了 JS Framework 承当了重要的职责,使得上层具备统一性,可以支持跨三个平台。总的来说它主要负责是:管理Weex的生命周期,解析JS Bundle,转为Virtual DOM,再通过所在平台不同的API方法,构建页面;进行双向的数据交互和响应。

打包与发布

在打包方案上,Weex和React Native都通过 Webpack 来打包bundle 文件的。不过,React Native打包如果不做拆分,打出的包是很大的,因而会自己制定一些拆包的规则。而Weex 作为React Native之后出现的跨平台实现方案,自然可以站在前人的肩膀上优化问题,比如:Bundle文件过大问题。 Weex 选择使用JS Framework 集成到 WeexSDK的方式,一定程度减少了JS Bundle的体积,使得 bundle 里面只保留业务代码。

打包时,weex 是通过 webpack 打包出 bundle 文件的。bundle 文件的打包和 entry.js 文件的配置数量有关,默认情况下之后一个 entry 文件,自然也就只有一个bundle文件。

在 weex 项目的 webpack.common.conf.js 中可以看到,其实打包也是区分了 webConfig 和 weexConfig 的不同打包方式。

Flutter

Flutter是Google用以帮助开发者在Ios和Android两个平台开发高质量原生应用的全新移动UI框架。与 React Native 和 Weex 框架使用的Javascript 技术不同,Flutter 使用的是全新的编程语言Drat,所以执行时并不需要 Javascript 引擎,但实际效果最终也通过原生渲染。

Flutter框架

Flutter框架主要分为 Framework 和 Engine两层,我们基于Framework 开发App主要运行在 Engine 上。Engine 是 Flutter 的独立虚拟机,由它适配和提供跨平台支持,目前猜测 Flutter 应用程序在 Android 上,是直接运行 Engine 上 所以在是不需要Dalvik虚拟机。其架构图如下图所示:

移动跨平台开发深度解析_flutter_08

得益于 Engine 层,Flutter 甚至不使用移动平台的原生控件, 而是使用自己 Engine 来绘制 Widget (Flutter的显示单元),而 Dart 代码都是通过 AOT 编译为平台的原生代码,所以 Flutter 可以 直接与平台通信,不需要JS引擎的桥接。

移动跨平台开发深度解析_bundle_09

而Flutter唯一要求系统提供的是canvas,用以实现UI的绘制。不过,Flutter 上 Android 自带了 Skia,Skia是一个 2D的绘图引擎库,跨平台,所以可以被嵌入到 Flutter的 iOS SDK中,也使得 Flutter Android SDK要比 iOS SDK小很多。

对比

下面对上面介绍的几大框架进行一个简单的对比,以方便读者进行对比学习。

对比类型

React Native

Weex

Flutter

实现技术

JavaScript

JavaScript

原生编码,无桥接

引擎

JS V8

JSCore

Flutter engine

使用语言

React

Vue

Dart

bundle文件大小

默认单一、较大

较小、多页面可多文件

不需要

上手难度

稍高大

容易

简单

框架难度

较重

较轻


支持对象

Android、IOS

Android、IOS、Web

Android、IOS

包大小对比

上面Apk大小是通过 react-native init、weex create 和 flutter 创建出的工程后,直接不添加任何代码,打包出来的 release 签名 apk 大小。从下图可以看出,其中大比例都是在so库。

移动跨平台开发深度解析_flutter_10

附:
​​​React Native中文网​​​
​​​Weex官方文档​​​
​​​Flutter中文社区​