总结一下分为:应用层、框架层、系统服务层、内核层四个部分。内核层主要是宏内核的 Linux 和微内核的 LiteOS,以及各项硬件驱动程序。框架层和系统服务层联系比较紧密,有横向的框架和纵向的服务,细分成很多相对独立的子系统,UI 框架和 Ability 框架都在这里。
注意这是 OpenHarmony 的架构,不含 GMS 和 HMS、不含 AOSP 也不支持安卓 API,与华为手机中推送的 HarmonyOS 版本是有差别的,下文展开对比。
▐ HarmonyOS 和 OpenHarmony 的区别
中文名都叫做「鸿蒙」,背后还是有一些区别的。HarmonyOS 是华为研发的商用操作系统,OpenHarmony 是华为捐献给开放原子开源基金会(OpenAtom)的开源版本,两个版本之间有交叉部分,概念上有混淆,所以很多讨论都在“自研”和“套壳”之间摇摆不定。
我尝试整理了各项概念之间的关系,如下图所示。非官方图,仅供参考,一切以官方口径为准。
首先鸿蒙应用是打包成 HAP (Harmony Ability Package) 格式分发的,和安卓 APK 一样都是压缩包,包结构大同小异。分发到端上之后,统一由鸿蒙(概念)的 API 承接,然后就分了不同的模式。在华为手机上推送的鸿蒙版本,可以无感兼容安卓应用,肯定是依赖了 AOSP 的。而开源的部分主要面向低端的 IoT 设备,或者手表手环之类的,甚至可以没有屏幕,2021年6月开源的版本刚支持了内存 128MB 以上的设备,还无法独立支撑手机上的应用。这是“套壳”和“自研”的两个极端场景,都未必是稳定的最终状态。而介于两者之间,总架构里框架层和系统服务层的各项子系统,是比较独立解耦的,能同时用在这些场景中,但是功能还不够健全,一部分已经开源了,比如微内核和 JS UI 框架,还有一部分未开源。兼容安卓不是长久之计,开源部分功能简陋,那么中间不确定的这一块会不会是未来的主链路呢?鸿蒙未来会不会真的发展成一个自主研发的全平台操作系统呢?拭目以待吧。
那么可以尝试回答社区里讨论鸿蒙最多的问题:
**鸿蒙是不是自研的?肯定有自研部分,也肯定依赖一些(国外)三方代码。**现阶段要求所有代码都自研是不现实的,不是一两年能完成的,肯定要先站在巨人的肩膀上发挥创新,然后进一步减少依赖,处理好协议和版权问题即可。
鸿蒙是否依赖 AOSP?可以说依赖,也可以说不依赖。HarmonyOS 的兼容模式是依赖 AOSP 的,不然没办法兼容安卓应用;开源的 OpenHarmony 是不依赖 AOSP 的,但目前主要用在一些低端 IoT 设备上。
如果没有特殊说明,下文中提到的「鸿蒙」都特指 OpenHarmony。
鸿蒙的 UI 子系统
鸿蒙的开源版本中没有 Java UI(至少我没找到),实现 JS UI 的框架叫 ACE,是 Ability Cross-platform Environment 的简称,有两个仓库:ace_engine_lite [2] 对应 ACE 旧架构,ace_ace_engine [3] 对应 ACE 新架构,两者的实现原理和上层语法都是不同的。
▐ 源码目录结****构
OpenHarmony 开源代码托管在 gitee 上,组下有两百多个仓库,建议按照官方源码获取的文档下载代码,源码目录结构和各仓库的对应关系可以参考 manifest 仓库的配置文件[4]。
如果你的环境已经配置好,执行下面这两行命令即可:
repo init -u gitee.com/openharmony… -b master --no-repo-verifyrepo sync -c
整个过程耗时 15~30min 左右,可以在执行完第一行命令后,找到 manifest 配置文件,移除 linux 和一部分 third_party 的依赖,会更快一点。
简略版目录结构如下(略过无关目录,仅展开相关部分):
OpenHarmony/ ├── applications # 应用程序样例 ├── base # 基础软件服务子系统集 ├── docs ├── domains # 增强软件服务子系统集 ├── drivers # 驱动子系统 ├── foundation # 系统基础能力子系统集 │ ├── aafwk # Ability 框架 │ ├── ace # JS UI 框架 │ │ ├── ace_engine # ACE 1.0, 仓库名: ace_ace_engine │ │ └── ace_engine_lite # ACE 2.0, 仓库名: ace_engine_lite │ ├── appexecfwk # 用户程序框架和包管理模块 │ └── graphic # 图形库相关的子系统 ├── interface │ └── sdk-js # JS API 的 TS 类型文件 ├── kernel # LiteOS 内核 │ ├── liteos_a │ └── liteos_m ├── test └── third_party
▐ 新旧 ACE 框架
下面是 ACE 框架的架构图[5],现在文档里描述的、IDE 里支持编写的都是这套 UI,提供的是类似小程序的 DSL[6] 开发 UI,HML(HarmonyOS Markup Language) + CSS + JS。
看起来和主流 JS + 原生渲染框架的架构都相似,提供了编译工具将源码编译成 js bundle,运行时有个在 JS 引擎启动后默认执行的 framework.min.js 文件,实现节点构建逻辑,然后在 C++ 层实现了一套 UIKit,支持的组件大概有二三十个。
在另一个不带 lite 后缀的仓库里(ace_ace_engine),还有一套 UI 系统的实现,架构层次更加简洁,技术更加先进,应该是新一代的 UI 框架,这是 新的 ACE 框架的架构图[7]:
这套架构主体分为应用、框架、引擎以及跨平台适配这几部分,应用层就是透出给开发者的语法,有好几种模式,下文详解。框架层实现了前端框架常见的组件化、MVVM 能力,能够响应式的更新 UI。下面是 JS 引擎,使用的是 QuickJS,应该也支持 V8。再向下是渲染引擎,包含了核心的渲染管线、动画、事件和各种布局绘制算法。
最下面的 porting layer 是适配多平台的关键,定义了平台无关的 layer 数据结构,可以提交给不同的合成器(Compositor)合成渲染,从代码上看,是支持 Flutter Engine 的。
整体渲染过程
▐ 容器创建和管理
鸿蒙 UI 的基本单位是 FA (Feature Ability),对应一个 AceAbility 的实例,提供了生命周期的钩子,在执行 OnStart() 的时候会在内部创建一个 AceContainer。但是这个 AceContainer 不由 AceAbility 直接持有,而是由全局唯一的单例 AceEngine 管理。
AceContainer 提供了 UI 渲染的各项能力,是个总的管理类,提供了生命周期和功能调度接口,内部划分了许多子模块:
-
Frontend: 前端代码的执行环境,JS 或者 JSON,有这层抽象或许还可以支持其他脚本引擎。
-
TaskExecutor: 单一线程内的任务管理器,类似其他渲染引擎中的 TaskRunner。
-
AssetManager: 资源管理器,可用于加载 JS 代码、图片、字体等资源。应该也具备缓存能力。
-
PipelineContext: 渲染管线的管理类,监听 vsync 回调刷新内部脏节点的布局、绘制、渲染。
-
AceView: 渲染生成的 UI 根节点,可以贴到外层容器中。
-
PlatformResRegister: 平台资源的注册和管理,以及部分通信接口,可在这里实现同层渲染功能。
▐ JS UI 开发框架
在 Frontend 部分,有三种不同类型的实现,分别是基于渲染指令的命令式 UI、声明式 UI、以及无需脚本引擎,用 JSON 文件渲染的 UI。命令式 UI 和 声明式 UI 都是用 QuickJS 作为脚本引擎,声明式 UI 里包含了 V8 的抽象,但是这部分好像没有开源出来。
- JS Frontend (命令式 UI)
「命令式 UI」是从引擎实现角度的说法,因为底层是基于渲染指令的,真正透出给开发者的语法,可以对接声明式、响应式的前端框架,比如 Vue.js。鸿蒙官网的 JS UI 背后对应的就是 JSFrontend,语法和小程序很像,应该是从之前的快应用继承而来,每个组件对应了 xx.hml 的模板文件、xx.css 的样式文件、xx.js 的脚本文件以及 xx.json 配置文件。
/* xxx.css */.container { flex-direction: column; margin-top: 20px; margin-left: 30px;}.title-text { color: #1a1a1a; font-size: 50px;}
// xxx.jsexport default { data: { headTitle: 'Capture the Beauty in This Moment' }}
上述代码将会打包编译成一个 js 文件,动态加载执行。
在初始化 JS 执行环境时,会先绑定一系列的原生接口,然后执行内置的 js framework,然后动态加载执行用户的 js 代码。在 js frontend 的 qjs_engine.cpp [8] 文件中,可以看到向 JS 环境中注册的接口:
因为 QuickJS 是支持 ES6 Module 的,这些接口是注册到了 ace 的模块中,可以用 import 导入,环境中也有 ace 这个全局环境:
import * as ace from 'ace'
// 创建 body 节点ace.domCreateBody(0, 'div', {/* attrs /}, {/ styles /}, {/ events */})
ACE 中实际发送渲染指令的代码在 third_party_jsframework 的 TaskCenter.ts[9] 中,接口的设计以及渲染指令的格式都与 alibaba/weex 的 TaskCenter.js 基本相同,也用 JS 模拟了简版 DOM API,上面对接了小程序风格的 DSL,这类技术社区里已经有大量实践,不再展开介绍。
- Declarative Frontend (声明式 UI)
这方面公开的资料不多,从代码上来看,并不是用 HML + CSS 的方式构建 UI,而是像 Flutter 一样用原子化的布局函数组合 UI,Flutter 里是用 Widget,鸿蒙的声明式 JS UI 用的是 JSView。再结合 jsproxyClass.js 里的代码来分析,提供了 ECMAScript 规范中的装饰器/注解辅助编程,预估代码写起来是这个样子的:
@Componentclass MyDemo { Column( Text('Hello World!'), Text('Some text here') .fontsize(36) .color(Color.Red) ).center()}
这里是 JSView 的源代码,平铺展示出来如下所示,目前的种类还不是很多。
这种方式的优势是组合性比较好,各布局节点不会像 CSS 那样互相影响,布局算法比较高效。但是抛弃了前端喜爱的 HTML + CSS 的布局方式,现存的各种前端框架、组件库、样式库没法直接迁移过来,生态建设会有些艰难。
- Card Frontend (无脚本 UI)
叫 CardFrontend 有点奇怪,可能是主要用在鸿蒙桌面 Widget 这种卡片化场景里吧。它是不依赖脚本引擎的,而是由特定格式的 JSON 文件驱动渲染。
JSON 文件的格式还没有明确的文档,这里有个测试用例,能看到主体分为 template、styles、actions、data 这几部分,模板里可以写花括号的数据绑定 {{some.data}},里边可以写简单的 JS 表达式,也不是完全静态的。而且 CardFrontend 类上有 UpdateData() 接口,可以更新模板的数据,具备一定的动态化能力。
▐ 节点构建流程
上面的三种前端框架对应了不同的 UI 开发方式,但是后半段的节点构建、布局绘制算法都是同一条链路,而且是用 C++ 自绘渲染的,不依赖系统 UI,一致性比较好。把完整的节点构建流程画出来如下所示:
声明式 UI 的 JSView 是直接绑定到 JS 环境里的,由页面/卡片代码直接调度,JSView 有大量的派生类,分别对应了原子化的样式描述。JSView 树会生成一颗 Component 树,节点基本是一一对应的,比如 JSGrid 会生成 GridLayoutComponent、JSText 会生成 TextComponent。
JSFrontend 和 CardFrontent 都是合成了渲染指令(C++),一个由前端框架 diff 生成,一个直接从 JSON 解析,然后构建内部一棵精简的 DOM 树(C++),注意这棵所谓 DOM 树是没有绑定到 JS 环境里的,没法通过 JS API 直接操作。每个 DOM 节点都会再生成一个 Component 节点。
Component 的派生类 RenderComponent 是可以直接生成 RenderNode 的,算是一条更短的链路吧。比如 ImageComponent 就继承自 RenderComponent。另一个派生类是 ComposedComponent,是由其他 Component 组合而来的。Element 也是相似的数据结构,派生了 RenderElement 和 ComposedElement。Component 可以构建 Element 和 RenderNode,Element 可以构建 RenderNode,坦白讲我还没看明白这两棵树的差异,未来是不是可以把 Component 和 Element 合并呢?
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!