作者 | 俞家欢
低头需要勇气,抬头需要实力
移动端跨平台技术
跨平台概念是软件开发中一个重要的概念,即不依赖于操作系统,也不依赖硬件环境。一个操作系统下开发的应用,放到另一个操作系统下依然可以运行。
移动端为什么需要跨平台技术
众所周知目前移动端主要有两大体系,一个是 Google 公司引领的 Android 体系,一个是 Apple 公司引领的 iOS 体系。两大平台应用开发技能树完全不同,因此当需要开发一个移动端应用时,需要在此两种系统体系上分别开发一遍。每一端都需要独立研发、测试,包括后续的维护工作也是如此,这样就会造成开发工作量和开发人员成倍增涨,增加研发费用和研发周期,可能会拖慢产品迭代的节奏。为了实现“偷懒”和“省钱”的目标,移动端跨平台技术便应运而生,目的是能够减小多端带来的开发成本,可以实现用一套代码跑通不同的移动平台。
移动端跨平台技术划分
移动端跨平台需要从两个维度去跨平台,一个是渲染层面,另一个逻辑层面。跨平台技术既可以是两个维度都跨平台,也可以只选一个维度进行跨平台。跨平台技术发展至今主要是是下面三种划分:
Web 容器 渲染使用系统原生提供的浏览器控件 (iOS 为 UIWebView/WKWebView,Android 为 WebView) 渲染 HTML5 页面,逻辑层面使用 JavaScript,定义 JS 与 原生代码的交互协议,将原生系统能力暴露给 H5。优点是可以开发效率高、维护成本低、具有动态化,缺点是渲染、性能与兼容性较差。代表框架为 Cordova。
原生渲染,逻辑跨平台 渲染使用各自系统提供的渲染方式,逻辑层使用特定语言将其编译成各端可以识别的产物去执行。代表框架为 ReactNative、Weex,代表语言为 Kotlin、C/C++。
自绘引擎,逻辑跨平台 渲染使用自建的跨平台绘制引擎,逻辑层使用特定语言将其编译成各端可以识别的产物去执行。代表框架为 Flutter。
Flutter 介绍
Flutter 是 Google 开发的跨平台 UI 框架,当前支持移动端、Web 以及桌面端。Flutter 使用 Dart 作为开发语言,底层使用 Skia 渲染引擎。
Skia(渲染)
Skia 是一款用 C++ 开发的、开源的、跨平台图形库。Skia 在图形转换、文字渲染、位图渲染方面都表现卓越,并提供了开发者友好的 API。
Flutter 架构于 Skia 之上 ,也因此拥有了彻底的跨平台渲染能力。通过与 Skia 的深度定制及优化,Flutter 可以最大限度地抹平平台差异,提高渲染效率与性能。底层渲染能力统一了,上层开发接口和功能体验也就随即统一了,开发者也就不用操心平台相关的渲染特性。也就是说,Skia 保证了同一套代码调用在各个平台上的渲染效果是完全一致的。Android 系统中默认集成了 Skia 绘制引擎,所以在打包 Android 产物时,不需要打包 Skia 引擎,这也是产物大小比 iOS 小的原因。
Dart(逻辑)
Dart 是快速开发多端应用的客户端优化语言。其目标是为多端开发提供高效的编程,并为应用程序框架提供灵活的执行运行时平台。
编译器
Dart 有两大类编译器
Native 平台
针对移动端以及桌面端设备应用。Dart Native 包括使用即时编译(JIT)的 Dart VM 和用于生成机器码的预先编译(AOT)编译器。
在应用开发过程中,高效开发至关重要。Dart VM 提供了即时编译(JIT),具有增量式编译(热更新/热重启)的特性以及丰富的调试支持。当应用程序准备部署到生产环境中时,Dart AOT 编译器可以提前编译成本地 ARM 或 x86_64 机器码。AOT 编译的代码运行在一个高效的 Dart 运行时中,该运行时强制开启类型安全检查,并使用快速对象分配和分代垃圾收集器管理内存。
Web 平台
针对 Web 应用。Dart Web 包括开发时编译器(dartdevc,增量开发)和生产时编译器(dart2js),两者都会将 Dart 代码翻译成 JavaScript 语言。
"线程"模型
在 Dart Native 中,线程被抽象成了 isolate。每个 isolate 独享内存,包含两个事件队列和一个事件循环,执行过程如下图所示:
优先执行 MicroTaskQueue 里的任务,MicroTaskQueue 中的任务都执行完了,再去执行 EventQueue 任务,EventQueue 中每执行完一个任务,都会去检查 MicroTaskQueue 是否有任务,有任务的话,就会优先循环执行完 MicroTaskQueue。
Event Queue
负责处理 I/O 事件、绘制事件、手势事件、接收其他 isolate 消息等事件。
Microtask Queue
事件的优先级比 Event Queue高,Flutter 内部会将手势识别、文本输入、滚动视图、保存页面等一些高优先级的事件放在此事件队列中。
异步任务
Dart 为 Event Queue 的任务建立提供了一层封装,叫作 Future,借助 await 与 async 关键字,可以通过事件循环实现非阻塞的同步等待。Dart 会将调用体的函数也视作异步函数,将等待语句的上下文放入 Event Queue 中,一旦有了结果,事件循环就会把它从 Event Queue 中取出,等待代码继续执行。
Flutter 技术架构
Flutter 在技术架构分为三层,从上至下依次是 Dart Framework, C/C++ Engine,Platform-specific Embedder。
Framework
用 Dart 编写,封装整个 Flutter 框架的核心功能,封装整个 Flutter 架构的核心功能,包括 Widget、动画、绘制、手势等功能,有Material(Android 风格 UI)和 Cupertino(iOS 风格)的 UI 界面,可构建 Widget 控件以及实现UI布局。
Engine
用 C++ 编写,用于高质量移动应用的轻量级运行时环境,实现了 Flutter 核心库,包括 Dart 虚拟机、动画和图形、文字渲染、通信通道、事件通知、插件架构等。
Embedder
平台中间层。
在不同的平台上,Framwork 和 Engine 是一样的,Embedder 中间层的实现是不同的。
Task Runner
Engine 不创建和管理线程,会要求 Embedder 创建 Platform、UI、Raster(GPU)以及 IO 四个 Task Runner 供 Engine 使用。Engine 并不关心这四个 Task Runner 分别具体跑在哪个线程中。但是为了获得最佳性能,Embedder 应该为每个 Task Runner 专门创建一个线程。
Platform Task Runner
对应平台(Android、iOS)的主线程,负责Embedder 和 Engine 层的交互,处理平台的消息。
UI Task Runner
对应平台(Android、iOS)的子线程,负责 Flutter Engine 执行 Root Isolate 中的所有 Dart 代码(所有 Dart Framework 层的代码都在此 Runner 中运行),执行渲染与处理 Vsync 信号,将 Widget 转换生成 Layer Tree。
Raster Task Runner
对应平台(Android、iOS)的子线程,真正执行渲染任务,光栅化所有从 UI Task Runner 中提交过来的任务,最终渲染到屏幕上。
IO Task Runner
对应平台(Android、iOS)的子线程,从磁盘/网络中读取图片,将图片数据进行处理为 GPU Task Runner 所需的格式进行渲染。
当前 iOS 和 Android 平台 上每个 Flutter Engine 实例启动都会为 UI、Raster 以及 IO 创建新线程,所有 Engine 实例共享一个 Platform Task Runner 和线程。
Platform Channel
Flutter 官方提供了一种 Platform Channel 的方案,用于 Dart 和平台之间相互通信。其核心流程是:
Flutter 通过 Platform Channel 将传递的数据编码成消息的形式发送到平台。
平台接收到 Platform Channel 的消息后,调用相应平台的 API。
执行完成后将结果数据通过同样方式原路返回。
Platform Channel 涉及三个要素:
Channel