境搭建與HAP構(gòu)建實(shí)踐)
在OpenHarmony上跑Flutter聽(tīng)起來(lái)像個(gè)繞口令可這卻是跨平臺(tái)開發(fā)者目前最值得花幾天時(shí)間跟進(jìn)的方向之一。我參加的這個(gè)開源鴻蒙跨平臺(tái)訓(xùn)練營(yíng)Day1目標(biāo)很樸素把Flutter界面跑到鴻蒙模擬器里面親眼確認(rèn)Dart寫的UI能渲染在OpenHarmony系統(tǒng)上。聽(tīng)起來(lái)只是“跑起來(lái)”三個(gè)字真正走下來(lái)才發(fā)現(xiàn)從Flutter SDK分支、DevEco Studio、OpenHarmony SDK到模擬器和HAP打包每一環(huán)都可能卡住。這篇文章就是按我第一天的實(shí)際操作順序整理出來(lái)的把環(huán)境、創(chuàng)建工程、啟動(dòng)模擬器、構(gòu)建運(yùn)行、日志排查這一整條鏈路完整過(guò)一遍也把踩過(guò)的坑和判斷思路寫清楚方便同樣從Flutter轉(zhuǎn)過(guò)來(lái)的同學(xué)直接照做。1. 為什么是Flutter for OpenHarmony跨平臺(tái)版圖上的最后一塊在動(dòng)手之前我先做了個(gè)簡(jiǎn)單的盤點(diǎn)。Flutter在Android、iOS、Web、桌面端的生態(tài)已經(jīng)非常成熟但OpenHarmony這一席在很長(zhǎng)一段時(shí)間里是懸空的。OpenHarmony自身主推的ArkUI是聲明式語(yǔ)法跟Flutter的Widget寫法在思路上有些相近但如果我已經(jīng)有一個(gè)用Dart寫的跨平臺(tái)業(yè)務(wù)指望團(tuán)隊(duì)把UI層用ArkUI重新寫一遍成本和技術(shù)風(fēng)險(xiǎn)都不可控。Flutter for OpenHarmony項(xiàng)目解決的就是這個(gè)核心痛點(diǎn)讓同一套Dart代碼和Widget樹最終編譯成鴻蒙系統(tǒng)的HAP包運(yùn)行而不是另起爐灶再維護(hù)一套UI。1.1 ArkUI和Flutter的關(guān)系比想象中微妙真要對(duì)比起來(lái)ArkUI的聲明式寫法、狀態(tài)管理機(jī)制和Flutter有不少相似之處很多Flutter開發(fā)者去看ArkUI文檔并不會(huì)覺(jué)得陌生。但這只是“語(yǔ)法相似”底層組件庫(kù)、路由、生命周期、插件生態(tài)完全是另一套東西。如果從零開始一個(gè)純鴻蒙項(xiàng)目用ArkUI開發(fā)效率并不低可對(duì)于已經(jīng)存在的Flutter工程理想方案顯然是保留原來(lái)的Dart業(yè)務(wù)代碼和Widget樹只是把渲染目標(biāo)和平臺(tái)通道切到OpenHarmony。Flutter for OpenHarmony的本質(zhì)就是Flutter引擎向OpenHarmony系統(tǒng)的移植。渲染層對(duì)接OpenHarmony的圖形接口Dart運(yùn)行時(shí)對(duì)接對(duì)應(yīng)的編譯產(chǎn)物而MethodChannel、PlatformView這些基礎(chǔ)能力改成走鴻蒙側(cè)的原生實(shí)現(xiàn)最終產(chǎn)出能被OpenHarmony安裝運(yùn)行的HAP。1.2 Day1為什么非要從模擬器切入我也想過(guò)直接上真機(jī)但訓(xùn)練營(yíng)第一天完全沒(méi)必要。模擬器的好處是環(huán)境可控、便于截圖、方便排查最重要的是能強(qiáng)制走一遍“編譯到HAP→安裝→啟動(dòng)→渲染→日志”的完整鏈路??缙脚_(tái)適配這類項(xiàng)目最大的坑往往不是業(yè)務(wù)代碼本身而是工具鏈銜接。Flutter官方SDK不認(rèn)OpenHarmonyOpenHarmony側(cè)也沒(méi)有Flutter的構(gòu)建產(chǎn)物兩邊都缺一截。Day1用模擬器剛好可以把這個(gè)“缺一截”的問(wèn)題暴露出來(lái)后面幾天再聊組件通信、PlatformView、性能調(diào)優(yōu)就都有了基礎(chǔ)。下面這些步驟我建議你也按順序走不要跳步。2. 環(huán)境準(zhǔn)備DevEco Studio、OpenHarmony SDK與Flutter適配分支的組合Flutter跑在鴻蒙模擬器上第一步不是寫代碼而是把兩條工具鏈接到一起。這里最容易被坑的點(diǎn)是不要用Flutter官網(wǎng)的官方SDK要用OpenHarmony SIG維護(hù)的flutter_flutter適配分支。官方SDK的目標(biāo)平臺(tái)列表里沒(méi)有OpenHarmony用它創(chuàng)建項(xiàng)目根本不會(huì)生成ohos目錄后面的一切都無(wú)從談起。2.1 完整工具清單我環(huán)境里最終裝齊的是下面這些版本匹配關(guān)系可以參考表格工具建議版本說(shuō)明DevEco Studio4.0 Release或更新打開鴻蒙工程、管理模擬器、下載SDKOpenHarmony SDK對(duì)應(yīng)4.0 Release在DevEco Studio的SDK Manager中下載ohpm隨SDK安裝OpenHarmony的包管理器依賴Node.js運(yùn)行hdc隨SDK附送鴻蒙調(diào)試連接工具類似adb日志和安裝都要用Flutter SDKOpenHarmony適配分支建議從openharmony-sig的flutter_flutter倉(cāng)庫(kù)拉取這里需要特別強(qiáng)調(diào)一下版本匹配。網(wǎng)絡(luò)上的教程時(shí)間跨度很大有的還在用OpenHarmony 3.2模擬器鏡像和SDK都不太一樣照抄容易翻車。穩(wěn)妥的做法是以你安裝的DevEco Studio版本為基準(zhǔn)讓OpenHarmony SDK、模擬器系統(tǒng)鏡像和flutter_flutter的分支盡量保持同一代比如都用4.0系。這樣至少能避開“API版本過(guò)低導(dǎo)致Dart VM初始化失敗”的這類問(wèn)題。2.2 Flutter適配分支怎么裝拉取適配分支命令行操作如下git clone -b 4.0適配分支tag https://gitee.com/openharmony-sig/flutter_flutter.gitclone完成后把flutter_flutter/bin目錄加進(jìn)PATH環(huán)境變量。在命令行里執(zhí)行flutter --version如果能正常打印版本號(hào)說(shuō)明Flutter側(cè)工具就緒。這個(gè)分支和官方SDK的區(qū)別在于flutter_tools被修改過(guò)支持--platforms ohos參數(shù)和flutter build hap命令也內(nèi)置了OpenHarmony側(cè)構(gòu)建產(chǎn)物需要的模板。2.3 不要忽略的兩個(gè)小工具第一是Node.jsohpm依賴它運(yùn)行建議裝Node 18以上的穩(wěn)定版本第二是hdc它通常藏在DevEco Studio的SDK目錄下例如DevEco Studio目錄/sdk/default/openharmony/toolchains/hdc把這個(gè)目錄也加進(jìn)PATH后面hdc list targets、hdc install、hdc shell hilog都會(huì)用到。我在Day1前期就是沒(méi)把hdc放進(jìn)PATH導(dǎo)致后面想用命令行裝HAP時(shí)還要臨時(shí)找路徑很不順手。環(huán)境配完后可以用flutter doctor -v掃一遍。看到Android toolchain報(bào)錯(cuò)不用慌那是給Android用的在OpenHarmony場(chǎng)景下可以忽略重點(diǎn)看Flutter工具本身是否來(lái)自適配分支以及系統(tǒng)是否能識(shí)別到OpenHarmony側(cè)的hdc工具鏈。3. 創(chuàng)建和集成Flutter項(xiàng)目從flutter create到鴻蒙工程嵌套環(huán)境裝好之后下一步是創(chuàng)建項(xiàng)目。這一步我一直強(qiáng)調(diào)要用適配分支下的flutter命令而不是系統(tǒng)里原來(lái)裝的Flutter。如果PATH里兩個(gè)Flutter同時(shí)存在務(wù)必確認(rèn)當(dāng)前生效的是flutter_flutter分支。判斷方法很簡(jiǎn)單查看flutter --version輸出的版本號(hào)里是否帶ohos相關(guān)的標(biāo)識(shí)或者直接看flutter create --help里有沒(méi)有ohos平臺(tái)選項(xiàng)。3.1 一行命令創(chuàng)建工程flutter create --platforms ohos day1_demo正常情況下這條命令會(huì)生成一個(gè)包含lib/main.dart、pubspec.yaml、ohos/目錄的Flutter工程。ohos/目錄里就是鴻蒙側(cè)的殼工程里面有entry模塊、build-profile.json5、oh-package.json5這些文件。沒(méi)有ohos/目錄說(shuō)明當(dāng)前flutter不是適配分支需要退回上一步重新檢查。生成后的關(guān)鍵結(jié)構(gòu)如下day1_demo ├── lib │ └── main.dart ├── ohos │ ├── entry │ ├── build-profile.json5 │ └── oh-package.json5 └── pubspec.yaml3.2 把工程按鴻蒙思路打開在DevEco Studio里直接File Open選擇剛才生成的ohos目錄而不是整個(gè)day1_demo。這是因?yàn)镈evEco Studio只識(shí)別OpenHarmony工程結(jié)構(gòu)ohos/目錄才是標(biāo)準(zhǔn)的鴻蒙側(cè)殼子。第一次打開時(shí)IDE會(huì)提示配置SDK路徑選擇你通過(guò)SDK Manager下載的OpenHarmony SDK即可。等待IDE完成Sync如果build-profile.json5和oh-package.json5沒(méi)有紅色報(bào)錯(cuò)說(shuō)明鴻蒙側(cè)工程已經(jīng)被正確識(shí)別。這里補(bǔ)充一個(gè)常見(jiàn)問(wèn)題如果執(zhí)行flutter create時(shí)生成了默認(rèn)的MainActivity這類文件不要混進(jìn)鴻蒙工程。Flutter for OpenHarmony的模板中入口是entry/src/main/ets/pages/Index.ets一類的Ability頁(yè)面需要調(diào)用FlutterView承載Dart頁(yè)面和Android的Activity概念不一樣。不要把Android思維硬套過(guò)來(lái)。3.3 已有OpenHarmony主工程怎么集成如果手上已經(jīng)有一個(gè)OpenHarmony主工程而不是從模板新建那就要走模塊依賴的路子?;舅悸肥前袴lutter工程作為依賴模塊掛到主工程里。具體做法是先創(chuàng)建好Flutter模塊然后主工程的build-profile.json5里添加對(duì)應(yīng)的模塊依賴并在entry的依賴配置中引用Flutter產(chǎn)物。Day1階段我不太建議大家一上來(lái)就搞這種復(fù)雜集成因?yàn)槟阈枰瑫r(shí)理解Flutter構(gòu)建和鴻蒙模塊依賴兩套體系排查問(wèn)題難度會(huì)翻倍。我的建議是先用模板工程跑通跑通了再嘗試遷移進(jìn)自己的主工程。3.4 第一次構(gòu)建前的幾個(gè)檢查項(xiàng)在啟動(dòng)模擬器之前建議先打開ohos/entry/src/main/module.json5和build-profile.json5看一眼。重點(diǎn)檢查包名是否包含非法字符、最低API版本是否和模擬器系統(tǒng)版本匹配。比如模擬器是OpenHarmony 4.0而模板里minAPIVersion寫的是9那一般沒(méi)問(wèn)題但如果版本差距過(guò)大運(yùn)行時(shí)會(huì)出現(xiàn)方法和資源找不到的詭異問(wèn)題。還有一個(gè)容易踩的細(xì)節(jié)檢查entry模塊是否被設(shè)置為可安裝和可啟動(dòng)否則HAP安裝成功也無(wú)法顯示圖標(biāo)。4. 在鴻蒙模擬器上跑起來(lái)啟動(dòng)、構(gòu)建與日志排查環(huán)境通了、工程建了接下來(lái)就是整個(gè)Day1的重頭戲啟動(dòng)模擬器構(gòu)建HAP把Flutter跑在鴻蒙模擬器上。模擬器啟動(dòng)這一步會(huì)比想象中慢第一次創(chuàng)建模擬器還要額外下載系統(tǒng)鏡像一定要有耐心。4.1 創(chuàng)建并啟動(dòng)本地模擬器在DevEco Studio里打開Device Manager找到Local Emulator選項(xiàng)卡新建一個(gè)模擬器設(shè)備。設(shè)備類型選Phone系統(tǒng)鏡像選擇你已經(jīng)下載好的OpenHarmony版本。如果鏡像列表是空的點(diǎn)下載按鈕幾GB的鏡像會(huì)花些時(shí)間。模擬器啟動(dòng)后桌面上會(huì)出現(xiàn)一個(gè)標(biāo)準(zhǔn)的OpenHarmony系統(tǒng)界面這時(shí)候可以打開終端執(zhí)行hdc list targets能看到模擬器設(shè)備編號(hào)說(shuō)明hdc連接正常。如果這里看不到設(shè)備大概率是hdc版本和模擬器不匹配或者模擬器還在啟動(dòng)中??梢缘葞酌朐賵?zhí)行一次。模擬器啟動(dòng)失敗在Windows環(huán)境里比較常見(jiàn)多數(shù)和虛擬化有關(guān)。如果啟動(dòng)時(shí)提示CPU加速不可用去BIOS確認(rèn)VT-x已經(jīng)開啟或者檢查Windows的虛擬機(jī)監(jiān)控程序功能。這一步和Android模擬器類似但OpenHarmony模擬器對(duì)硬件要求的報(bào)錯(cuò)提示做得還比較原始不會(huì)直接告訴你“去開Hyper-V”你得自己排查。4.2 用IDE構(gòu)建并運(yùn)行到模擬器模擬器起來(lái)后最簡(jiǎn)單的方式就是在DevEco Studio里選中entry模塊直接點(diǎn)Run。IDE會(huì)自動(dòng)完成編譯、打包、安裝和啟動(dòng)。這個(gè)方式的好處是錯(cuò)誤信息展示得比較全Gradle或ohpm層面的報(bào)錯(cuò)能看到定位鏈接。如果你習(xí)慣命令行也可以這樣做flutter build hap --debug構(gòu)建完成后產(chǎn)物一般在Flutter工程的build/hap/目錄下。拿到HAP文件后用hdc安裝hdc install build/hap/entry-default-signed.hap安裝成功后在模擬器桌面找到應(yīng)用圖標(biāo)點(diǎn)擊啟動(dòng)即可。4.3 只看該看的日志hilog和Dart層的報(bào)錯(cuò)我第一天在日志查看上浪費(fèi)了不少時(shí)間這里把我的方法分享出來(lái)。OpenHarmony側(cè)的系統(tǒng)日志用hilog查看類似adb logcat。要過(guò)濾Flutter運(yùn)行時(shí)日志可以執(zhí)行hdc shell hilog | grep flutter更暴力一點(diǎn)可以同時(shí)監(jiān)聽(tīng)render和error關(guān)鍵字hdc shell hilog | grep -E flutter|Dart|ERROR如果Flutter代碼里有未捕獲的Dart異常通常會(huì)在日志中看到類似e/flutter開頭的條目。比如搜索熱詞里那個(gè)典型的報(bào)錯(cuò)格式E/flutter (31173): [error:flutter/runtime/dart_vm_initializer.cc(41)] unhandled exception這個(gè)報(bào)錯(cuò)本身并不神秘它就是Dart層拋出了未捕獲異常常見(jiàn)原因是調(diào)用了某個(gè)平臺(tái)通道能力但當(dāng)前環(huán)境沒(méi)實(shí)現(xiàn)。你真正要定位的是后面跟著的那一行通常會(huì)說(shuō)明異常類型和來(lái)源文件比如MissingPluginException或TypeError。我的排查方法是先去pubspec.yaml里看有哪些依賴插件確認(rèn)哪些插件沒(méi)有ohos平臺(tái)的實(shí)現(xiàn)。適配OpenHarmony早期階段不少原生插件只有Android/iOS端實(shí)現(xiàn)調(diào)用到這些通道就會(huì)報(bào)unhandled exception。解決辦法要么換成支持ohos的插件要么在業(yè)務(wù)代碼里做平臺(tái)判斷跳過(guò)不支持的調(diào)用。4.4 典型的黑屏問(wèn)題排查鏈路我遇到的情況是應(yīng)用圖標(biāo)能點(diǎn)開但Flutter界面一直黑屏日志里看不到明顯的Dart異常。這時(shí)我的排查順序是先看模擬器系統(tǒng)版本和API級(jí)別排除版本不匹配再看hilog里是否有渲染線程相關(guān)錯(cuò)誤然后檢查是不是沒(méi)有加載Flutter引擎。最終發(fā)現(xiàn)是模板工程的Flutter渲染區(qū)域高度寫成0了入口頁(yè)面的布局容器沒(méi)有給FlutterView分配合法的尺寸。這種事在Android開發(fā)里也常有鴻蒙的布局容器規(guī)則和Android不完全一樣寬高必須顯式指定或通過(guò)約束撐開不能單純依賴wrap_content的思維慣性。這類問(wèn)題如果到時(shí)候也困擾你建議一行一行檢查入口ETS頁(yè)面的布局代碼確認(rèn)承載Flutter的組件寬高值正常。另外如果模擬器上啟用的是OpenHarmony默認(rèn)的圖形后端一些GL相關(guān)的警告可以暫時(shí)忽略不影響UI顯示就不算致命。5. 模擬器之外Day1復(fù)盤與下一步驗(yàn)證思路第一天的目標(biāo)到這里就已經(jīng)完成了Flutter工程成功跑在鴻蒙模擬器上Dart界面的渲染、點(diǎn)擊響應(yīng)、日志輸出都正常。但跑通只是起點(diǎn)有幾個(gè)問(wèn)題必須從Day1就心里有數(shù)。5.1 模擬器和真機(jī)的差異要提前知道我現(xiàn)在是在x86_64架構(gòu)的本地模擬器上調(diào)試而大多數(shù)接入鴻蒙的正式設(shè)備是ARM架構(gòu)??缂軜?gòu)調(diào)試最直接的影響是一些涉及底層性能的指標(biāo)在模擬器上參考意義有限。比如CPU密集型的計(jì)算任務(wù)、圖形渲染幀率、IO性能都會(huì)和真機(jī)有明顯差別。另外攝像頭、傳感器、NFC這一類和硬件強(qiáng)相關(guān)的能力模擬器基本覆蓋不全這些必須靠真機(jī)來(lái)驗(yàn)。Day1能通過(guò)模擬器確定的是工具鏈、構(gòu)建鏈路、Dart代碼運(yùn)行和基本UI布局這幾件事不依賴具體硬件模擬器驗(yàn)證是充分的。5.2 PlatformView和組件通信是后面幾天的重頭戲把Flutter跑起來(lái)之后緊接著要考慮的是業(yè)務(wù)落地的問(wèn)題。鴻蒙原生控件和Flutter混合渲染需要用到PlatformView這部分在OpenHarmony側(cè)還在持續(xù)完善和Android端成熟度有明顯差距。組件通信也會(huì)變成一個(gè)高頻話題Flutter側(cè)的MethodChannel怎么和鴻蒙側(cè)的Ability交互事件如何回傳這些不是模擬器上簡(jiǎn)簡(jiǎn)單單能驗(yàn)證完的需要結(jié)合具體業(yè)務(wù)場(chǎng)景。訓(xùn)練營(yíng)后面的內(nèi)容我估計(jì)都會(huì)圍繞這些展開到時(shí)候我會(huì)接著記錄實(shí)操過(guò)程。5.3 第一天的一點(diǎn)個(gè)人建議如果你和我一樣是從Flutter遷移過(guò)來(lái)第一天別貪多跑通一個(gè)最小工程就夠了。我在這個(gè)過(guò)程中最深的體會(huì)是跨平臺(tái)適配的第一阻力從來(lái)不是Dart語(yǔ)言本身而是工具鏈和平臺(tái)的思維方式差異。模擬器給了我們一個(gè)低成本的試煉場(chǎng)但千萬(wàn)別把模擬器上的成功當(dāng)成全部后續(xù)真機(jī)測(cè)試、性能分析、平臺(tái)通道兼容性驗(yàn)證這些硬仗還得一場(chǎng)一場(chǎng)打。整個(gè)Day1我最有成就感的時(shí)刻不是看到Hello World界面渲染出來(lái)的那一秒而是那之后用hilog把完整的啟動(dòng)日志翻出來(lái)一條條梳理明白的時(shí)刻??缙脚_(tái)開發(fā)里最值錢的能力其實(shí)就是這種“能跑起來(lái)也知道它為什么能跑起來(lái)”的確定性。后面幾天的訓(xùn)練營(yíng)我會(huì)繼續(xù)沿著這條路線走下去。