鴻蒙是華為研發的新一代終端作業系統,能適用於 IoT、手錶、手機、Pad、電視等各種型別的裝置上,扛起“國產作業系統”的大旗,也遭受了很多非議。2021 年 6 月初發布了 OpenHarmony 2。0 Canary 版本,開源了更多子系統的程式碼,支援記憶體 128MB 以上的裝置。其中就包含了新版本的 JS UI 框架,本文重點分析這部分程式碼。(文章內容僅供參考,如有任何描述不準確的內容,感謝大家後臺留言探討與指正~)

鴻蒙系統概述

系統架構分層

建議去 OpenHarmony 官網[1] 上了解更多資訊,下面是官方的技術架構圖:

關於鴻蒙系統 JS UI 框架原始碼的分析

總結一下分為:應用層、框架層、系統服務層、核心層四個部分。核心層主要是宏核心的 Linux 和微核心的 LiteOS,以及各項硬體驅動程式。框架層和系統服務層聯絡比較緊密,有橫向的框架和縱向的服務,細分成很多相對獨立的子系統,UI 框架和 Ability 框架都在這裡。

注意這是 OpenHarmony 的架構,不含 GMS 和 HMS、不含 AOSP 也不支援安卓 API,與華為手機中推送的 HarmonyOS 版本是有差別的,下文展開對比。

HarmonyOS 和 OpenHarmony 的區別

中文名都叫做「鴻蒙」,背後還是有一些區別的。HarmonyOS 是華為研發的商用作業系統,OpenHarmony 是華為捐獻給開放原子開源基金會(OpenAtom)的開源版本,兩個版本之間有交叉部分,概念上有混淆,所以很多討論都在“自研”和“套殼”之間搖擺不定。

我嘗試整理了各項概念之間的關係,如下圖所示。

非官方圖,僅供參考,一切以官方口徑為準。

關於鴻蒙系統 JS UI 框架原始碼的分析

首先鴻蒙應用是打包成 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 https://gitee。com/openharmony/manifest。git -b master ——no-repo-verify

repo 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 UI 框架原始碼的分析

看起來和主流 JS + 原生渲染框架的架構都相似,提供了編譯工具將原始碼編譯成 js bundle,執行時有個在 JS 引擎啟動後預設執行的 framework。min。js 檔案,實現節點構建邏輯,然後在 C++ 層實現了一套 UIKit,支援的元件大概有二三十個。

在另一個不帶 lite 字尾的倉庫裡(ace_ace_engine),還有一套 UI 系統的實現,架構層次更加簡潔,技術更加先進,應該是新一代的 UI 框架,這是 新的 ACE 框架的架構圖[7]:

關於鴻蒙系統 JS UI 框架原始碼的分析

這套架構主體分為應用、框架、引擎以及跨平臺適配這幾部分,應用層就是透出給開發者的語法,有好幾種模式,下文詳解。框架層實現了前端框架常見的元件化、MVVM 能力,能夠響應式的更新 UI。下面是 JS 引擎,使用的是 QuickJS,應該也支援 V8。再向下是渲染引擎,包含了核心的渲染管線、動畫、事件和各種佈局繪製演算法。

最下面的 porting layer 是適配多平臺的關鍵,定義了平臺無關的 layer 資料結構,可以提交給不同的合成器(Compositor)合成渲染,從程式碼上看,是支援 Flutter Engine 的。

整體渲染過程

容器建立和管理

鴻蒙 UI 的基本單位是 FA (Feature Ability),對應一個 AceAbility 的例項,提供了生命週期的鉤子,在執行 OnStart() 的時候會在內部建立一個 AceContainer。但是這個 AceContainer 不由 AceAbility 直接持有,而是由全域性唯一的單例 AceEngine 管理。

關於鴻蒙系統 JS UI 框架原始碼的分析

AceContainer 提供了 UI 渲染的各項能力,是個總的管理類,提供了生命週期和功能排程介面,內部劃分了許多子模組:

Frontend

: 前端程式碼的執行環境,JS 或者 JSON,有這層抽象或許還可以支援其他指令碼引擎。

TaskExecutor

: 單一執行緒內的任務管理器,類似其他渲染引擎中的 TaskRunner。

AssetManager

: 資源管理器,可用於載入 JS 程式碼、圖片、字型等資源。應該也具備快取能力。

PipelineContext

: 渲染管線的管理類,監聽 vsync 回撥重新整理內部髒節點的佈局、繪製、渲染。

AceView

: 渲染生成的 UI 根節點,可以貼到外層容器中。

PlatformResRegister

: 平臺資源的註冊和管理,以及部分通訊介面,可在這裡實現同層渲染功能。

JS UI 開發框架

關於鴻蒙系統 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

配置檔案。

{{headTitle}}

/* xxx。css */

。container {

flex-direction: column;

margin-top: 20px;

margin-left: 30px;

}

。title-text {

color: #1a1a1a;

font-size: 50px;

}

// xxx。js

export default {

data: {

headTitle: ‘Capture the Beauty in This Moment’

}

}

上述程式碼將會打包編譯成一個 js 檔案,動態載入執行。

在初始化 JS 執行環境時,會先繫結一系列的原生介面,然後執行內建的 js framework,然後動態載入執行使用者的 js 程式碼。在 js frontend 的 qjs_engine。cpp [8] 檔案中,可以看到向 JS 環境中註冊的介面:

關於鴻蒙系統 JS UI 框架原始碼的分析

因為 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 規範中的裝飾器/註解輔助程式設計,預估程式碼寫起來是這個樣子的:

@Component

class MyDemo {

Column(

Text(‘Hello World!’),

Text(‘Some text here’)

。fontsize(36)

。color(Color。Red)

)。center()

}

這裡是 JSView 的原始碼,平鋪展示出來如下所示,目前的種類還不是很多。

關於鴻蒙系統 JS UI 框架原始碼的分析

這種方式的優勢是組合性比較好,各佈局節點不會像 CSS 那樣互相影響,佈局演算法比較高效。但是拋棄了前端喜愛的 HTML + CSS 的佈局方式,現存的各種前端框架、元件庫、樣式庫沒法直接遷移過來,生態建設會有些艱難。

Card Frontend (無指令碼 UI)

叫 CardFrontend 有點奇怪,可能是主要用在鴻蒙桌面 Widget 這種卡片化場景裡吧。它是不依賴指令碼引擎的,而是由特定格式的 JSON 檔案驅動渲染。

JSON 檔案的格式還沒有明確的文件,這裡有個測試用例,能看到主體分為

template

styles

actions

data

這幾部分,模板裡可以寫花括號的資料繫結

{{some。data}}

,裡邊可以寫簡單的 JS 表示式,也不是完全靜態的。而且 CardFrontend 類上有

UpdateData()

介面,可以更新模板的資料,具備一定的動態化能力。

節點構建流程

上面的三種前端框架對應了不同的 UI 開發方式,但是後半段的節點構建、佈局繪製演算法都是同一條鏈路,而且是用 C++ 自繪渲染的,不依賴系統 UI,一致性比較好。把完整的節點構建流程畫出來如下所示:

關於鴻蒙系統 JS 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 合併呢?

RenderNode 是真正的佈局節點,其他渲染引擎叫 RenderObject 或者 LayoutObject,派生了很多子類,實現了各式各樣的佈局演算法,型別設計和 Flutter 的 Widget 接近。RenderNode 先佈局再繪製,產出的資料結構是 Layer Tree,這一層在鴻蒙裡是用來跨平臺的,也就是官方架構圖裡的 Porting Layer[7],提交給不同的合成器就可以跨不同的平臺。

比較有意思的是,鴻蒙的 third_party 裡有 flutter,而且實現了一個 FlutterSceneBuilder [10],porting layer 的資料結構和 Flutter Engine 的 layer 也基本一致,在 flow 裡還添加了 ohos_layers 的程式碼。換句話說,鴻蒙的新的 ACE 框架或許可以跑在 Flutter Engine 之上,Flutter Framework 或許也可以跑在鴻蒙的 Graphic UI 上。

佈局繪製渲染

實現渲染管線的核心類是 PipelineContext,內部儲存了多份髒節點的佇列,當節點屬性發生變化時,不會立即觸發佈局和繪製,而是加入到快取的佇列裡。同時監聽系統 vsync 訊號,每幀按序 flush 內部記錄的髒節點,依次執行動畫檢測、節點重新構建、重新佈局、重新繪製、重新整理游標等流程。

關於鴻蒙系統 JS UI 框架原始碼的分析

核心程式碼是截圖中的這兩段,有所簡化,每幀 vsync 會觸發

OnVsyncEvent()

回撥,如果 surface 已經初始化好,則依次重新整理內部的髒節點,如果包含動畫,則呼叫

RequestFrame()

請求觸發下一幀 vsync。首先 dirty Elements 會觸發

Rebuild()

重新構建 RenderNode,並且呼叫

MarkNeedLayout()

把它加到待佈局的佇列裡,然後

FlushLayout

會重新整理這個佇列,呼叫節點的

OnLayout()

方法計算佈局,然後把自身再加入到待繪製的佇列裡,然後

FlushRender()

依次呼叫節點的

Repaint()

方法重新繪製,生成 layer tree。

探討鴻蒙的發展

在鴻蒙開發者大會上還貼過這麼一張圖,手機、平板、PC 的裝置數已經很久沒有大幅增長了,在未來五年裡也沒有多少增量。但是 IoT 裝置,比如手錶、線下大屏、車機等裝置越來越多,IoT 裝置數還處於快速發展的階段。鴻蒙的判斷未來是萬物互聯的時代,目標是打造「面向多智慧終端、全場景的分散式作業系統」。

與 AliOS 相比,鴻蒙誕生在了合適的時間,有足夠大的空間。

關於鴻蒙系統 JS UI 框架原始碼的分析

鴻蒙系統的微核心系統,硬體驅動和子系統是可插拔的,可以很好的相容從低端到高階、形態差異很大的 IoT 裝置。還有一個特點就是 Ability 的跨裝置流轉,發揮華為在通訊技術上的優勢,多個硬體裝置可以在系統層直連,說不定可以催生出一些新的玩法。

對於新興事物,一直拿舊的事物做類比,看到的只有與舊事物相同的部分,沒有看到的才是真正的價值所在。如果鴻蒙只相容安卓,沒有差異化的功能,就沒有太大的發展意義,只解決了“國產化”的情懷。也很難講 Android/iOS 相比於前一代諾基亞摩托羅拉在技術上有什麼革命性的顛覆,鴻蒙的設計理念還是有一些亮點的。系統級能力的創新,要有個過程才能被應用層感知到。鴻蒙也許只是個開頭,要想帶來一波爆發式的發展,可能需要喬布斯這樣的人物,把新的系統能力轉化成新一代互動、新一代產品。再考慮國家政策和技術自研的趨勢,鴻蒙未來可期。

參考連結

OpenHarmony 官網:

https://www。

openharmony。cn/

ACE Lite:

https://

gitee。com/openharmony/a

ce_engine_lite

ACE Engine:

https://

gitee。com/openharmony/a

ce_ace_engine

配置檔案:

https://

gitee。com/openharmony/m

anifest/blob/master/default。xml

ACE 架構圖:

https://

gitee。com/openharmony/a

ce_engine_lite/blob/OpenHarmony-2。0-Canary/figures/JS%E5%BA%94%E7%94%A8%E5%BC%80%E5%8F%91%E6%A1%86%E6%9E%B6。png

類小程式 DSL:

https://

device。harmonyos。com/en

/docs/apiref/js-framework-file-0000000000616658

新 ACE 架構圖:

https://

gitee。com/openharmony/a

ce_ace_engine/blob/OpenHarmony-2。0-Canary/figures/JS-UI%E6%A1%86%E6%9E%B6%E6%9E%B6%E6%9E%84。png

qjs_engine。cpp:

https://

gitee。com/openharmony/a

ce_ace_engine/blob/OpenHarmony-2。0-Canary/frameworks/bridge/js_frontend/engine/quickjs/qjs_engine。cpp#L1925-1940

TaskCenter。ts:

https://

gitee。com/openharmony/t

hird_party_jsframework/blob/OpenHarmony-2。0-Canary/runtime/main/manage/event/TaskCenter。ts#L127-133

FlutterSceneBuilder:

https://

gitee。com/openharmony/a

ce_ace_engine/blob/OpenHarmony-2。0-Canary/frameworks/core/pipeline/layers/flutter_scene_builder