OpenHarmony工程:從環(huán)境搭建到HAP打包全攻略)
1. 先把核心鏈路說清楚Flutter 是怎么跑到 OpenHarmony 上的如果你和我一樣第一次在 Flutter 工程里執(zhí)行flutter build hap時盯著屏幕等了幾分鐘最后拿到一個.hap文件第一反應多半是這個文件到底是怎么從那一堆 dart 和 ets 文件里變出來的我在把項目遷移到 OpenHarmony 平臺之前也以為只是“加一個構建目標”而已真正動手才發(fā)現(xiàn)這背后的工程目錄結構、構建工具鏈和產(chǎn)物組織形式跟 Android/iOS 都有不少微妙的差異。這篇就是把我從環(huán)境搭建到編譯打包、再到踩坑排錯的完整過程整理出來給準備用 Flutter 編譯開發(fā) OpenHarmony 工程的同學做參考。先說結論OpenHarmony 現(xiàn)在能跑 Flutter靠的不是原版 Flutter SDK而是 OpenHarmony SIG 維護的 flutter_flutter 分支。這個分支在 Flutter 官方工具鏈里加入了 ohos 這個 target用來生成 OpenHarmony 工程骨架并支持把 Dart 代碼、Flutter engine 和原生插件一起打包成 HAPHarmonyOS Ability Package。換句話說你寫的還是 Dart跑的還是 Flutter 那套自繪渲染引擎但宿主環(huán)境換成了 OpenHarmony 的 Ability 生命周期。1.1 一個容易誤解的事實原版 Flutter SDK 編譯不出 HAP很多人剛接觸時會問我已經(jīng)裝好 Flutter 了為什么flutter create出來的工程里沒有 ohos 目錄原因很簡單原版 Flutter SDK 根本不認識 OpenHarmony 這個平臺。你需要在環(huán)境變量里把 flutter 指向 OpenHarmony SIG 發(fā)布的 flutter_flutter 分支然后執(zhí)行flutter doctor時才會出現(xiàn) ohos 相關的狀態(tài)項創(chuàng)建工程時也才能帶上--platforms ohos這樣的參數(shù)。這個分支本質(zhì)上是在 flutter_tools 層面加了 ohos 平臺支持包括工程模板、構建命令、打包邏輯。所以版本對齊非常關鍵flutter_flutter 分支的版本要和你本地的 OpenHarmony SDK 版本配套。版本錯配的時候最常見的表現(xiàn)就是創(chuàng)建工程能成功但構建時報各種“找不到 Native API”或者“so 庫加載失敗”的錯。我后面會在常見問題里專門展開。1.2 Flutter 與 OpenHarmony 的對接層從 Dart 到 ArkTS 的橋接思路運行時鏈路是理解整個工程目錄的關鍵。OpenHarmony 上的 Flutter 應用本質(zhì)上是一個 OpenHarmony 應用進程里跑了一個 Flutter engine。EntryAbility加載一個 Flutter 容器頁Flutter engine 以動態(tài)庫的形式打進 HAPDart 代碼通過 engine 執(zhí)行UI 由 Flutter 自繪引擎渲染不走 ArkUI 的組件樹。你在工程里寫的ets文件主要負責 Ability 生命周期、系統(tǒng)能力接入和與 Dart 側(cè)的信令交互而真正業(yè)務界面基本都在lib目錄的 Dart 代碼里。這種模型帶來的直接影響是工程目錄會同時存在 Flutter 和 OpenHarmony 兩套原生骨架。你既要維護pubspec.yaml的依賴也要維護oh-package.json5和module.json5這些 OpenHarmony 側(cè)的配置。很多首次接觸的人就是被這個“雙軌制”搞暈的。2. 環(huán)境準備版本對齊、工具鏈安裝、初始化一個可編譯的工程2.1 工具鏈清單與版本匹配關系我的建議是先把下面這幾樣東西裝齊再談創(chuàng)建工程組件作用我當前使用的版本區(qū)間flutter_flutterOpenHarmony 分支提供 ohos 平臺構建能力跟隨 SIG 發(fā)布的最新 release 分支OpenHarmony SDK提供 ArkTS 編譯、SDK API、簽名工具5.0 系列對應 API 12DevEco StudioIDE主要用來管理 SDK、簽名和真機調(diào)試5.0 及以上ohpmOpenHarmony 包管理器安裝原生依賴隨 DevEco Studio 或獨立安裝hvigor構建工具執(zhí)行 HAP 打包任務隨工程模板聲明版本Node.jshvigor 腳本運行依賴建議 18 以上這里要特別強調(diào)版本匹配不是只看“最新”而是看 flutter_flutter 分支的說明文檔里推薦的組合。官方 README 一般會寫清楚當前分支適配 OpenHarmony 的哪個 API Level。我自己就常年踩這個坑升級 OpenHarmony SDK 后忘了同步升級 flutter_flutter 分支結果構建出的 HAP 在真機上啟動后直接白屏。2.2 環(huán)境變量配置與首次 create 工程環(huán)境變量方面除了把 flutter 的 bin 目錄加進 PATH還需要確認 OpenHarmony SDK 的本地路徑能被構建工具找到。我習慣在用戶環(huán)境變量里顯式聲明OHOS_SDK_HOME指向 DevEco Studio 內(nèi)置的 SDK 目錄如果你用命令行工具鏈建議把 ohpm 和 hvigor 的 bin 目錄也一起加進 PATH。配置完環(huán)境變量有個很常見的坑新開的終端才能生效已經(jīng)在跑的終端窗口里執(zhí)行flutter --version還是老版本。這不是你沒配置好而是 PATH 的生效機制就是如此。重開終端后可以用下面幾條命令快速驗證flutter --version flutter doctor -v ohpm --versionflutter doctor -v輸出里如果能看到 ohos 相關項說明分支切換成功如果沒看到大概率是 flutter SDK 路徑?jīng)]有切到 flutter_flutter 分支。接下來創(chuàng)建工程flutter create --platforms ohos --org com.example my_app cd my_app創(chuàng)建完成后查看工程根目錄你會發(fā)現(xiàn)多了一個ohos目錄這就是 OpenHarmony 原生工程的載體。如果創(chuàng)建時忘了加--platforms ohos可以回到根目錄補執(zhí)行flutter create --platforms ohos .但注意不要覆蓋已有代碼。驗證工程能否跑起來最直接的方式是構建一個 debug 版 HAPflutter build hap --debug第一次構建會拉取 Gradle 依賴、hvigor 依賴還有 Flutter engine 的預編譯產(chǎn)物時間比較長是正常的。構建成功后用 DevEco Studio 連接真機或模擬器安裝即可。2.3 從創(chuàng)建工程到跑起 Demo 的完整驗證路徑我建議第一次別急著寫業(yè)務代碼先把默認模板跑通。跑通的意義在于環(huán)境鏈路是通的后續(xù)出了問題可以排除“工具鏈沒裝對”這個因素專心查業(yè)務代碼。跑通 Demo 的步驟拆開來是flutter create --platforms ohos創(chuàng)建工程。flutter build hap --debug構建出可安裝 HAP。DevEco Studio 打開工程配置簽名調(diào)試可以勾選自動簽名。連接真機點擊運行看到默認計數(shù)器頁面就是成功。這一步如果失敗不要繼續(xù)往下寫業(yè)務代碼先回頭排查工具鏈版本。我在第 5 章列了一些高頻報錯可以先對照看看。3. 工程目錄逐層拆解從根目錄到 ohos 子工程有哪些“暗樁”3.1 根目錄pubspec.yaml、.flutter-plugins-dependencies 與平臺目錄的對應關系Flutter 工程根目錄的重要性不需要多講但在 OpenHarmony 適配場景下有幾個文件需要額外關注。pubspec.yaml除了聲明 Dart 依賴還決定了 Flutter 插件的加載范圍。當你執(zhí)行flutter pub get后工程根目錄會生成.flutter-plugins-dependencies文件這個 JSON 文件里記錄了所有啟用的插件及其各平臺實現(xiàn)路徑。點擊進去能看到ohos字段它指向插件包里的 ohos 原生實現(xiàn)。如果你引入了一個第三方 Flutter 插件但發(fā)現(xiàn)構建 HAP 時沒有把對應的原生代碼編譯進去十有八九是這個文件里沒有 ohos 實現(xiàn)信息——原因可能是插件本身沒提供 ohos 支持或者插件版本太舊。再看平臺目錄。標準 Flutter 工程里android、ios目錄對應各平臺的原生外殼在 OpenHarmony 適配分支下多出來的ohos目錄承擔了類似職責。三者并列存在互不干擾。但要注意.metadata這個隱藏文件里記錄了當前工程的 Flutter 版本和生成工具版本如果你切了 flutter_flutter 的不同分支建議重新執(zhí)行一次flutter pub get必要時手動檢查這個文件里的版本信息避免遺留舊數(shù)據(jù)。3.2 ohos 子工程HAP 的構造骨架ohos目錄是整個工程里最值得花時間搞清楚的部分。它的結構跟 DevEco Studio 創(chuàng)建的 OpenHarmony 工程基本一致ohos/ ├── AppScope/ │ ├── app.json5 │ └── resources/ ├── entry/ │ ├── build/ │ ├── libs/ │ ├── oh-package.json5 │ ├── build-profile.json5 │ ├── hvigorfile.ts │ └── src/main/ │ ├── module.json5 │ ├── ets/ │ ├── resources/ │ └── ... ├── build-profile.json5 ├── hvigorfile.ts ├── oh-package.json5 └── local.propertiesAppScope是應用級配置app.json5里是應用包名、版本號、icon 等全局信息。entry是默認主模塊對應一個可獨立運行的 HAP。如果你后續(xù)要拆多個模塊可以在這個層級下繼續(xù)加模塊目錄。build-profile.json5分兩個層級外層工程級的負責配置簽名信息、模塊列表和 product 維度entry 內(nèi)層模塊級的負責當前模塊的編譯配置。簽名文件通常在ohos/entry/build-profile.json5里通過signingConfigs引用DevEco Studio 的自動簽名會幫你在~/.ohos/config/下維護個人信息文件不要手動改這些 local 配置除非你知道自己在做什么。module.json5是最容易出問題的文件。它聲明了 Ability、權限和 extension 信息。比如要接入相機、圖庫、支付這類系統(tǒng)能力需要在這里加requestPermissions權限聲明。Flutter 插件機制在 OpenHarmony 側(cè)也是通過這個文件里的 extension 配置來注冊的插件開發(fā)者在文檔里一般會注明需要在module.json5中添加什么片段漏了這一段插件編譯能過但運行時調(diào)用會直接失敗。3.3 lib 目錄組織與原生資源如何聯(lián)動Dart 側(cè)的lib目錄組織決定了后續(xù)業(yè)務擴展和原生橋接的復雜度。我自己的習慣是分成三層lib/pages/頁面級代碼只管 UI 和交互。lib/services/數(shù)據(jù)服務和平臺通道封裝比如本地數(shù)據(jù)庫、后端同步、網(wǎng)絡請求。lib/platform/平臺通道的接口定義和實現(xiàn)分發(fā)邏輯。為什么要單獨拆platform層因為 OpenHarmony 適配意味著你想調(diào)用的某些系統(tǒng)能力圖庫、支付、推送沒有現(xiàn)成 pub 包需要自己寫 platform channel。把接口隔離在platform/目錄下Dart 側(cè)業(yè)務只依賴抽象接口實現(xiàn)分別在ohos/entry/src/main/ets/里用原生代碼完成。這樣后續(xù)切換平臺或者升級原生實現(xiàn)都不需要改業(yè)務頁面。原生資源走的是 OpenHarmony 的資源管理機制而不是 Flutter 的assets。比如你要在原生側(cè)顯示一個啟動圖圖片放到entry/src/main/resources/base/media/下string 配置放到base/element/string.json。Flutter 側(cè)的圖片等資源依然放在工程根目錄的assets里通過pubspec.yaml聲明。兩套資源體系完全獨立記住這個規(guī)則找資源時就不會滿工程亂翻。3.4 Android/iOS 目錄與 ohos 目錄的異同做個對比方便有 Android 基礎的讀者快速遷移理解功能Android 目錄ohos 目錄應用級配置android/app/build.gradleAppScope/app.json5build-profile.json5模塊清單AndroidManifest.xmlsrc/main/module.json5入口組件MainActivityEntryAbility原生代碼app/src/main/java/src/main/ets/資源文件app/src/main/res/src/main/resources/簽名文件keystorep12 / cer / p7b包管理器Gradleohpm hvigor結構上可以說高度對應但在構建鏈路細節(jié)上完全不同。Android 用 Gradle 構建 APKOpenHarmony 用 hvigor 構建 HAP。你在 Flutter 里執(zhí)行的flutter build hap實際上就是 flutter_tools 調(diào)用 hvigor 的封裝。理解這個關系后面看日志排錯會快很多。4. 一次完整編譯產(chǎn)物在目錄間如何流轉(zhuǎn)并最終打成 HAP4.1 從 flutter build hap 到 HAP 落盤的關鍵流程命令行敲下flutter build hap --release之后構建鏈路大致是這樣走的flutter pub get解析 Dart 依賴生成.flutter-plugins-dependencies。Dart 代碼編譯。release 模式走 AOT 編譯產(chǎn)出libapp.sodebug 模式產(chǎn)出kernel_blob.bin。Flutter engine 和插件原生代碼參與編譯。插件里ohos/目錄下的代碼會被 hvigor 編譯成對應的.so庫。hvigor 讀取module.json5、build-profile.json5、資源和簽名配置把所有產(chǎn)物按 OpenHarmony 規(guī)范打包成 HAP。最終 HAP 落盤到build/ohos/或ohos/entry/build/下。從工程目錄的視角看這個流程里最關鍵的是第 2 步和第 3 步的產(chǎn)物去向。Flutter 的 AOT 編譯產(chǎn)物libapp.so會合并進 HAP 的libs/目錄插件編譯出的.so也會按架構放在對應目錄。如果你自定義了某個插件的原生實現(xiàn)改完代碼卻發(fā)現(xiàn) HAP 里沒有生效先檢查插件目錄下有沒有ohos子目錄、構建產(chǎn)物有沒有更新。4.2 構建產(chǎn)物目錄里到底有什么以我本地一個工程為例構建完成后主要產(chǎn)物分布在這幾個地方build/ ├── flutter-build/ # Flutter 中間產(chǎn)物 │ ├── app.so # AOT 編譯產(chǎn)物 │ └── flutter_assets/ # Dart 側(cè)資源 ohos/entry/build/ ├── default/ │ ├── outputs/ # 最終的 HAP 包 │ ├── intermediate/ # hvigor 中間產(chǎn)物 │ └── ...拿到 HAP 后你可以用 DevEco Studio 自帶的工具或直接改后綴為 zip 打開看結構。一個典型的 release HAP 里面會包含內(nèi)容說明libs/arm64-v8a/各種.so包括 libflutter.so、libapp.so、插件 soets/編譯后的 ArkTS 字節(jié)碼resources/OpenHarmony 側(cè)資源module.json編譯后的模塊配置pack.info打包信息看到這個結構你就明白為什么flutter build hap能一次搞定它把 Dart 運行時、Flutter 引擎和 OpenHarmony 原生外殼全部融合到了一個包體里。4.3 調(diào)試模式與 release 模式的差異調(diào)試模式下Dart 代碼不會提前 AOT 編譯而是以kernel_blob.bin的形式打進 HAP配合flutter attach實現(xiàn)熱重載。因此 debug HAP 的體積比 release 大不少啟動速度也會慢一些這是正常現(xiàn)象。有個細節(jié)值得注意OpenHarmony 上 Flutter 的熱重載前提是工程里的module.json5和插件注冊沒有被改壞。我遇到過一次熱重載失效排查了半天最后發(fā)現(xiàn)是module.json5里某個插件 extension 配置被 DevEco Studio 自動格式化時調(diào)整了位置重新聲明后恢復正常。release 模式下則是完全 AOTFlutter 引擎執(zhí)行的是機器碼性能和啟動速度都更接近原生應用。日常開發(fā)用 debug發(fā)版一定用 release這個習慣在 OpenHarmony 工程里同樣適用。5. 編譯與運行階段的高頻報錯根因、排查鏈路與規(guī)避方案5.1 版本錯配引發(fā)的“依賴下載不下來”與 Gradle 插件報錯先說我遇到最多的一類問題版本錯配。具體表現(xiàn)有兩種。第一種ohpm install或flutter pub get時拉取依賴失敗報網(wǎng)絡或校驗錯誤。OpenHarmony 生態(tài)的包管理走的是 ohpm 倉庫國內(nèi)網(wǎng)絡環(huán)境下偶爾會有倉庫地址不通的問題。處理方式是在~/.ohpm/.ohpmrc里配置官方推薦的鏡像源然后清理本地緩存重新 install。第二種執(zhí)行構建時報 Gradle 相關的錯誤。比如下面這類提示You are applying Flutters main Gradle plugin imperatively using the apply script這個報錯一般不是 OpenHarmony 工程本身的問題而是 Flutter 分支版本和舊版 Android 緩存配置發(fā)生沖突的典型表現(xiàn)。升級 Flutter 分支后老的android/settings.gradle或android/build.gradle里寫死了舊的插件應用方式構建時互相干擾。排查思路是檢查android/settings.gradle中的 plugin 配置是否和當前 Flutter 版本匹配。如果不需要 Android 構建直接把android目錄遷移或重生成一份。執(zhí)行flutter clean刪掉android/.gradle和build緩存重新構建。由于我們只關心 OpenHarmony 目標很多 Android 側(cè)的構建兼容問題可以繞過不用死磕。5.2 接入鴻蒙原生能力時的配置問題以圖庫、IAP 為例用 Flutter 調(diào)用鴻蒙的圖庫是社區(qū)里問得非常多的問題。思路很明確走 platform channel。Dart 側(cè)用MethodChannel發(fā)消息原生側(cè)在EntryAbility或?qū)iT建的PhotoService.ets里接收消息調(diào)用 OpenHarmony 的 PhotoViewPicker API再把結果傳回 Dart。工程配置上有一處非常容易遺漏module.json5里必須聲明對應的權限。{ module: { requestPermissions: [ { name: ohos.permission.READ_IMAGEVIDEO } ] } }漏掉權限清單編譯不會報錯但點擊按鈕后頁面無響應日志里會出現(xiàn)權限拒絕的信息。排查這類問題最有效的方式是把 DevEco Studio 的 HiLog 打開按進程過濾直接搜Permission關鍵字。IAP 支付類似。OpenHarmony 側(cè)的支付 SDK 需要你在module.json5聲明對應權限同時在oh-package.json5里引入支付 SDK 依賴。如果你只在 pub 層找了某個支付插件發(fā)現(xiàn)沒法拉起支付先檢查插件是否實現(xiàn)了 ohos 端再看 module 配置是否完整。很多支付插件只提供了 Android/iOS 實現(xiàn)在 OpenHarmony 上需要自己對接原生 SDK這種情況下插件的ohos目錄里應該有原生適配代碼。這類問題的通用排查順序是確認插件有沒有 ohos 實現(xiàn)。確認module.json5權限和 extension 聲明完整。寫一個最簡的測試頁面用一個固定 method 名調(diào)用原生邏輯驗證通道通不通。用 HiLog 查看原生側(cè)異常。5.3 資源文件改動不生效與熱重載失效的處理思路有同學在社區(qū)里反饋說修改了資源文件里的 HTML 或配置構建后界面沒變化懷疑是構建緩存的問題。這個現(xiàn)象在 OpenHarmony 工程里我遇到過幾次大多數(shù)情況下確實和增量構建緩存有關。處理方法是分層排查確認改的是 Flutter 側(cè)資源還是 OpenHarmony 側(cè)資源。Flutter 側(cè)資源改動后flutter clean再重新構建即可OpenHarmony 側(cè)資源改動后需要觸發(fā) hvigor 的重新編譯有時候要手動刪掉ohos/entry/build下的緩存目錄。確認資源文件命名是否符合規(guī)范資源名大小寫或非法字符可能導致編譯時資源被靜默忽略。如果界面沒變化但日志正常用 HAP 解包檢查 resources 里內(nèi)容是否更新。熱重載失效的另一個常見來源是module.json5被 DevEco Studio 和 flutter_tools 兩邊同時維護偶爾產(chǎn)生沖突。我的做法是原生側(cè)配置統(tǒng)一在 DevEco Studio 里改Dart 側(cè)統(tǒng)一在命令行或編輯器里改避免兩個工具交叉寫同一個文件的時間窗口。6. 我在目錄結構維護上的一些長期習慣6.1 目錄分層與多模塊管理的取舍OpenHarmony 工程的目錄結構和 Android 類似支持多模塊。但我的建議是除非你的工程確實有獨立編譯、獨立升級的業(yè)務模塊否則不要一上來就拆多個 module。多模塊帶來的構建鏈路復雜度指數(shù)上升尤其是 Flutter 插件和 hvigor 的配置交互很容易出現(xiàn)“模塊 A 能編過模塊 B 編不過”的詭異狀態(tài)。我自己偏向用單模塊 目錄分包的方式組織原生代碼ohos/entry/src/main/ets/ ├── entryability/ ├── pages/ ├── service/ └── plugin/service放系統(tǒng)能力封裝plugin放 Flutter 插件對應的原生實現(xiàn)。這樣既保持了職責清晰又不用承擔多模塊配置的額外成本。等業(yè)務規(guī)模真正到了需要獨立模塊的時候再按模塊拆也不遲。關于本地數(shù)據(jù)庫和后端同步很多 Flutter 項目會用到 sqlite 或 drift 這類方案在 OpenHarmony 上要確認插件是否支持 ohos 平臺。我目前的做法是把數(shù)據(jù)訪問層單獨放到lib/services/database/下用 sqflite 或 drift 的抽象接口如果某個插件沒有 ohos 實現(xiàn)就自己寫一個基于 OpenHarmony 關系型數(shù)據(jù)庫的適配層。目錄結構的價值在這里就體現(xiàn)出來了適配層有明確的位置不會散落在各個頁面里。6.2 給新人的上手清單與個人體會最后整理一份快速清單給第一次用 Flutter 編譯開發(fā) OpenHarmony 工程的同學確認用的是 OpenHarmony 適配版 Flutter SDK不是原版。確認 flutter_flutter 分支版本和 OpenHarmony SDK 版本匹配。flutter create --platforms ohos生成工程不要手動拼目錄。先構建默認 Demo 跑通再寫業(yè)務代碼。原生配置改完注意檢查module.json5權限和插件注冊都在這。構建異常優(yōu)先flutter clean 刪緩存排除緩存干擾再查代碼。我自己踩過最深的坑其實就是版本管理。Flutter 的 OpenHarmony 適配分支更新頻率不算低團隊協(xié)作時如果每個人拉的分支版本不一樣很容易出現(xiàn)“我這邊能編你那邊編不過”的情況。建議在工程根目錄用 git tag 或提交記錄把 flutter_flutter 分支的版本鎖定并在 README 里寫清楚當前各工具鏈的版本組合。工程目錄結構看著是靜態(tài)的靜態(tài)文件但它背后隱含的版本契約才是真正需要長期維護的東西。把這個約定做扎實后面所有編譯問題都能少一半。