(14.1) 跨端语言对比

(14.1) 跨端语言对比_第1张图片

  • weex rn
    • 这些都是使用jscore作为解释控制器,通过jscore和native的交互,引导native讲dom树渲染为原生控件树,同时jscore也能对逻辑代码进行处理,并在适当时机通知native刷新dom
    • 因为是原生控件,所以并不是所有的css语法都支持
    • weex更适合以单页面发布,是一种比较轻量级的方案,多页面问题较大,而rn比较适合整个模块的多页面编写
  • flutter
    • 自定义渲染,不依赖原生组件,只需要canvs画布,内置绘制引擎
    • dart编写,快速编译为原声语言,不需要js桥借
  • dynamic
    • dynamic也是使用了原生控件,提供了自定义表达式,组件,事件的能力,但是他的dom树是直接依赖原生管理器实现的,ios和Android实现了统一协议的视图生成管理器
    • 但是,由于缺乏jscore这样的逻辑处理器,就导致dynamic只能作为简单的页面展示组件,处理展示层面的显示动态化,而不能支持逻辑层面的动态化
    • 举个例子,小红点组件其实是个常见需求,对于weexrn,他们很容易实现点击消除红点,无非是绑定事件,事件修改属性,属性驱动渲染,渲染告知native,然后dom消除,,,但是由于dynamic没有自身的逻辑处理能力,你只能吧事件绑定给native,然后native要么遍历view树去直接直接修改view,要么遍历数据去修改数据然后重新对view绑定数据,但无论哪种方式其实本质上都打破了我们对dynamic最开始的设计原则,dynamic想要实现数据和视图对native的透明化,不依赖于发版(最后我们实现了一个自定义的组件,这个组件支持角标,点击就会消除。 嗯,是要保证数据不要重复渲染,因为数据是外部传进来的,我们其实并没有修改外部数据)
  • 小程序
    • 简单而言,就是webview的h5,但是常规的WebView是单线程模型的,渲染和逻辑在同一个线程,这就带来了很大的限制,因此重写WebView支持Render和Work线程的区分,同时对dom标签进行了自定义
    • 多个WebView的跳转来实现页面跳转
    • 与Naive的交互基本和H5和Native的交互方式一样,点击走shouldOverrideUrlLoading,资源请求走shouldInterceptRequest

一、RN

Facebook 出品;JavaScript语言;JSCore引擎;React设计模式;原生渲染。

Learn once, write anywhere

学习 react ,同时掌握 web 与 app 两种开发技能。React Native是利用 JS 来调用 Native 端的组件,从而实现相应的功能。

1.1 分层

(14.1) 跨端语言对比_第2张图片

  • JS层
  • C++ 实现的动态连结库(.so),作为中间适配层桥接
    • 实现了js端与原生端的双向通信交互。这里最主要是封装了 JavaScriptCore 执行js的解析,而 react native 运行在JavaScriptCore中,所以不存在浏览器兼容的问题。
  • Java Native层

1.2 实现原理

(14.1) 跨端语言对比_第3张图片

  • 渲染
    • 和前端开发不同:react native 所有的标签都不是真实控件,JS代码中所写控件的作用,类似 Map 中的 key 值。JS端通过这个 key 组合的 Dom ,最后Native端会解析这个 Dom ,得到对应的Native控件渲染,如 Android 中 标签对应 ViewGroup 控件。
  • 逻辑
    • 在 react native 中,JS端是运行在独立的线程中(称为JS Thread )。JS Thread 作为单线程逻辑,不可能处理耗时的操作。那么如 fetch 、图片加载 、 数据持久化 等操作,在 Android 中实际对应的是 okhttp 、Fresco 、SharedPreferences等。而跨线程通信,也意味着 Js Thread 和原生之间交互与通讯是异步的。
  • js与native交互
    • 原生端提供的各种 Native Module 模块(如网络请求,ViewGroup控件)
    • JS 端提供的各种 JS Module(如JS EventEmiter模块)
    • 两者都会在C++实现的so中保存起来,双方的通讯通过C++中的保存的映射,最终实现两端的交互。通信的数据和指令,在中间层会被转为String字符串传输,双向的调用流程如下图

(14.1) 跨端语言对比_第4张图片
(14.1) 跨端语言对比_第5张图片

1.3 打包

最终: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

Alibaba 出品;JavaScript语言;JS V8引擎;Vue设计模式;原生渲染。

Write once, run everywhere

写个 vue 前端,顺便帮你编译成性能还不错的 apk 和 ipa(当然,现实有时很骨感)。基于 Vue 设计模式,支持 web、android、ios 三端,原生端同样通过中间层转化,将控件和操作转化为原生逻辑来提高用户体验

2.1 结构

(14.1) 跨端语言对比_第6张图片
weex 比起react native,主要是在JS V8的引擎上,多了 JS Framework 承当了重要的职责,使得上层具备统一性,可以支持跨三个平台。

  • JSFrameWork
    • 管理Weex的生命周期;
    • 解析JS Bundle,转为Virtual DOM,再通过所在平台不同的API方法构建页面
    • 进行双向的数据交互和响应。
  • C++
  • Java Native

在 weex 中,主要包括三大部分:

  1. JS Bridge:WXBridgeManager
    • 主要用来和 JS 端实现进行双向通信,比如把 JS 端的 dom 结构传递给 Dom 线程。
  2. Render:WXRenderManager
    • 真实的渲染逻辑处理,负责在UI线程中对 dom 实现Native渲染
  3. Dom:WXDomManager
    • Dom 主要是用于负责 dom 的解析、映射、添加等等的操作,最后通知UI线程更新

三部分通过WXSDKManager统一管理。其中 JS Bridge 和 Dom 都运行在独立的 HandlerThread 中,而 Render 运行在 UI 线程。
(14.1) 跨端语言对比_第7张图片

2.2 实现原理

(14.1) 跨端语言对比_第8张图片
weex 中文件默认为 .vue ,而 vue 文件是被无法直接运行的,所以 vue 会被编译成 .js 格式的文件,Weex SDK会负责加载渲染这个js文件。

Weex可以做到跨三端的原理在于:在开发过程中,代码模式、编译过程、模板组件、数据绑定、生命周期等上层语法是一致的。不同的是在 JS Framework 层的最后,web 平台和 Native 平台,对 Virtual DOM 执行的解析方法是有区别的

  • 渲染
    • 和 react native一样——weex 所有的标签也不是真实控件,JS 代码中所生成存的 dom,最后都是由 Native 端解析,再得到对应的Native控件渲染,如 Android 中 标签对应 WXTextView 控件
    1. weex 接收到 js 文件以后,JS Framework 根据文件为 Vue 模式,会调用weex-vue-framework 中提供的 createInstance方法创建实例。(也可能是Rax模式);
    2. createInstance 中会执行 Js Entry 代码里 new Vue() 创建一个组件,通过其 render 函数创建出 Virtual DOM 节点;
    3. 由JS V8 引擎上解析 Virtual DOM ,得到 Json 数据发送至 Dom 线,这里输出 Json 也是方便跨端的数据传输;
    4. Dom 线程解析 Json 数据,得到对应的 WxDomObject,然后创建对应的WxComponent 提交 Render;
    5. Render在原生端最终处理处理渲染任务,并回调里JS方法。
    6. weex 在原生渲染 Render 时,在接收到渲染指令后,会逐步将数据渲染成原生组件。Render 通过解析渲染数据的描述,然后分发给不同的模块。比如:控件渲染属于 dom 模块中,页面跳转属于navigator模块等。模块的渲染过程并非一个执行完,再执行另一个的流程,而是类似流式的过程。如上一个 的组件还没渲染好,下一个的渲染又发了过来。这样当一个组件的嵌套组件很多时,或者可以看到这个大组件内的UI,一个一个渲染出来的过程

(14.1) 跨端语言对比_第9张图片

2.3 打包

weex 作为 react-native 之后出现的跨平台实现方案,自然可以站在前人的肩膀上优化问题,比如:Bundle文件过大问题。
Bundle文件的大小,很大程度上影响了框架的性能,而 weex 选择将 JS Framework 集成到 WeexSDK 中,一定程度减少了JS Bundle的体积,使得 bundle 里面只保留业务代码

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

三、Flutter

Google 出品;Dart语言;Flutter Engine引擎;响应式设计模式;原生渲染。

与 react native 和 weex 的通过 Javascript 开发不同,Flutter 的编程语言是Drat,所以执行时并不需要 Javascript 引擎,但实际效果最终也通过原生渲染。

(14.1) 跨端语言对比_第10张图片
Flutter 主要分为 Framework 和 Engine,我们基于Framework 开发App,运行在 Engine 上。Engine 是 Flutter 的独立虚拟机,由它适配和提供跨平台支持

目前猜测 Flutter 应用程序在 Android 上,是直接运行 Engine 上 所以在是不需要Dalvik虚拟机(这是比kotlin更彻底,抛弃JVM的纠缠?)

  1. 得益于 Engine 层,Flutter 甚至不使用移动平台的原生控件, 而是使用自己 Engine 来绘制 Widget (Flutter的显示单元)。Flutter 唯一要求系统提供的是 canvas,以实现UI的绘制
    • 在Flutter中,大多数东西都是widget,而widget是不可变的,仅支持一帧,并且在每一帧上不会直接更新,要更新而必须使用Widget的状态。
    • 无状态和有状态 widget 的核心特性是相同的,每一帧它们都会重新构建,有一个State对象,它可以跨帧存储状态数据并恢复它。
  2. 而 Dart 代码都是通过 AOT 编译为平台的原生代码,所以 Flutter 可以 直接与平台通信,不需要JS引擎的桥接。

(14.1) 跨端语言对比_第11张图片
Flutter 上 Android 自带了 Skia,Skia是一个 2D的绘图引擎库,跨平台,所以可以被嵌入到 Flutter的 iOS SDK中,也使得 Flutter Android SDK要比 iOS SDK小很多

  • Dart之所以成为Flutter不可或缺的一部分,根本原因还是因为其具有以下特性
    • Dart是AOT(Ahead Of Time)编译的,编译成快速、可预测的本地代码,使Flutter几乎都可以使用Dart编写。这不仅使Flutter变得更快,而且几乎所有的东西(包括所有的小部件)都可以定制;
    • Dart也可以JIT(Just In Time)编译,开发周期异常快,工作流颠覆常规(包括Flutter流行的亚秒级有状态热重载);
    • Dart可以更轻松地创建以60fps运行的流畅动画和转场。Dart可以在没有锁的情况下进行对象分配和垃圾回收。就像JavaScript一样,Dart避免了抢占式调度和共享内存(因而也不需要锁)。由于Flutter应用程序被编译为本地代码,因此它们不需要在领域之间建立缓慢的桥梁(例如,JavaScript到本地代码)。它的启动速度也快得多;
    • Dart使Flutter不需要单独的声明式布局语言,如JSX或XML,或单独的可视化界面构建器,因为Dart的声明式编程布局易于阅读和可视化。所有的布局使用一种语言,聚集在一处,Flutter很容易提供高级工具,使布局更简单;
    • 开发人员发现Dart特别容易学习,因为它具有静态和动态语言用户都熟悉的特性。

三、Dynamic

阿里、腾讯、美团等大厂基本都提出了自己的动态化组件能力,大意基本都是实现了 动态下发布局并绑定数据和事件的能力

  • 三要素

    • 模版:xml标签组成的文件
    • 控件
    • 表达式
    • 事件
  • 模版管理

    • 管理xml文件的缓存、记录和版本
  • 视图管理

    • 生成View,这个过程会读取动态属性和事件属性先缓存下来,并直接赋值静态属性
      1. XmlResourceParser xml解析器,用于解析指定xml文件
      2. 模仿LayoutInflater 视图创建过程 https://www.jianshu.com/p/c29ec7e79d0d
      3. LayoutInflater创建过程中,如果xml标签不存在“.”,意味着是系统控件,会调用onCreateView,在此处复写和拦截
    • 绑定数据
      • 根据数据赋值给对应动态属性,其中调用了表达式进行解析
  • 新版本

    • 动态属性ATS分词树直接在远端生成
    • xml模板预编译为AST,然后转二进制,类似VirtualView的思想,用字节区块来标示元素,编码后的文件客户端取出来可以直接还原为WidgetNode Tree。事实上,Android平台使用aapt编译xml资源文件时,也会进行类似的操作,可以参考AOSP中的相关代码
    • 扁平化
    • 提前测量
    • 2.0中已经去掉了内置ViewConstructor的反射,但业务方自定义ViewConstructor还是用的反射,3.0中所有的自定义view都会去除反射
      • 通过 维一标示的字符串编码实现映射关系存储

四、小程序

参考文献

  • 最火移动端跨平台方案盘点:React Native、weex、Flutter
  • RN和weex工作原理

你可能感兴趣的:(14-跨端)