本文基于真实移植过程整理。习惯树(grove)是一个 Rust + Tauri 2 的本地习惯养成应用,本次将其从 Windows 桌面版移植到 HarmonyOS 手机(nova 14,API 24), 全程真机验证通过。本文按「为什么能不改前端 → 环境 → 一步步怎么做 → 踩过的坑 →验证清单」的顺序写,命令均可直接复制执行。


好习惯,改变一生。好习惯,陪伴一生。习惯树是一款完全本地运行的习惯养成应用。坚持不必是枯燥的对勾与数字——它把每一次坚持可视化成一棵基于分形算法生成的「习惯树」,让坚持本身成为一种值得期待的小仪式。

项目地址*:https://gitcode.com/qq8864/habit-tree/tree/ohos

更多交流学习,欢迎加入开源鸿蒙PC社区https://harmonypc.csdn.net/

欢迎在PC社区平台申请新建项目https://atomgit.com/OpenHarmonyPCDeveloper

猫哥的博客https://blog.csdn.net/qq8864

🌱 开发缘由:好习惯,值得一棵树

我们都明白「习惯成自然」的道理,但真正把一件小事,日复一日地做下去,并不容易啊。有多少人能够克服阻力,坚持下去?但如果你每次的努力,都能有反馈和惊喜呢?

用过的习惯 App 不少,可大多数是「打卡按钮」——每天机械地按一下,久了之后,打卡本身反而成了一种负担。也试过Habitica 那种游戏化的养成,系统很完整,但对只想安静坚持的人来说,稍显复杂。

于是我想:我需要的不是又一个待办清单,而是一点仪式感——一个让坚持本身变得值得期待的东西。
在这里插入图片描述

文章目录


一、前言:移植前的心态

先说结论:** Tauri 应用移植到鸿蒙,前端 100% 不用改,Rust 后端交叉编译成 .so套个 DevEco 工程壳就行 **。原理我在第二节讲。但别高兴太早——这条路不是「跑一下脚本就完事」,中间有几个坑是会让人怀疑人生的:

  1. 启动即崩溃(SIGABRT):装了能装,启动 1 秒内直接 abort,没有任何界面。这个坑我花了最多时间,最后定位到是 Tauri 的 app_data_dir() 在鸿蒙上报错。
  2. 白屏 + “failed to request localhost:5173”:应用跑起来了,但加载的是开发服务器地址而不是打包好的前端——因为产物是 dev 模式。
  3. 一堆 Windows 专属的小坑:命令行工具拉不起 .bat、hdc 路径拼接、屏幕锁屏装不上、hilog 缓冲滚动太快抓不到崩溃……

好消息是:这台机器之前猫哥已经成功移植过 m3u8dl-tauri 和 cc-switch 两个开源项目,环境基本是齐的(工具链、SDK、DevEco、签名账号),所以本文假设你也有同样的前置环境。如果是全新机器,参考文末的速查表把环境装齐。


二、移植原理:为什么前端一行都不用改

Tauri App (Rust + WebView)
  → napi-ohos 桥接层(mobile_entry_point 宏展开需要)
  → OHOS ArkWeb WebView(渲染原前端,withGlobalTauri 全局桥)
  → HAP 打包(DevEco hvigor)
  • 前端(TypeScript + Vite 构建产物)原样塞进 HAP 的 rawfile 目录;
  • 后端 Rust 交叉编译成 libgrove_lib.so(aarch64 ELF),由 RustAbility@ohos-rs/ability)加载;
  • ArkWeb 用 tauri://localhost 这个自定义 scheme 提供页面,window.__TAURI__全局桥提供 IPC——所以前端代码里 invoke() 照常工作,零改动

关键在于用哪个 Tauri 分支:官方 feat/open-harmony 分支yangyongzhen/tauri,基于 tauri 2.11.5 = crates.io 官方最新版),配套tauri-apps/wrytauri-apps/taotauri-apps/cargo-mobile2 的官方 OHOS 分支。

能上官方分支,就别用社区 fork——社区 fork 停在 2.8.x,会把整个依赖树锁死在两年前。


三、前置环境:哪些东西必须提前备好

组件 要求 本次实际路径
开发机 Windows 10/11 x86_64 本机实测 Windows
Rust 1.75+,gnullvm 工具链(见下) stable-x86_64-pc-windows-gnullvm
Node.js 18+(DevEco 自带亦可) DevEco tools\node
OHOS SDK(NDK) HarmonyOS NEXT + D:\oh\DevEcoStudio\sdk\HarmonyOS-NEXT-DB6\openharmony
DevEco Studio 5.0+(自带 hvigor/ohpm/hdc/node) D:\Program Files\Huawei\DevEco Studio
真机 鸿蒙手机/平板,开发者模式 + USB 调试 nova 14(TLR-AL00,API 24)

关键点 1:gnullvm 工具链。Windows 上做鸿蒙交叉编译必须用stable-x86_64-pc-windows-gnullvm(基于 lld + compiler-rt),因为默认 GNU 工具链的链接器在 llvm-mingw 环境下找不到 -lgcc_eh/-lgcc

rustup toolchain install stable-x86_64-pc-windows-gnullvm
rustup target add --toolchain stable-x86_64-pc-windows-gnullvm aarch64-unknown-linux-ohos

关键点 2:SDK 有两种目录,别搞混

  • native\(NDK,只有 llvm 工具链 + sysroot)→ 交叉编译 Rust 用;
  • 完整 SDK(含 ets/js/native/toolchains)→ DevEco 打包 HAP 用。
    DevEco 的 SDK 设置必须指完整 SDK,指纯 NDK 会报 SDK component missing (00303168)

关键点 3:网络。fork 依赖要走 GitHub,配置代理:

git config --global url."https://ghfast.top/https://github.com".insteadOf "https://github.com"
# cargo 拉 git 依赖时用 git CLI(才能吃到上面的 rewrite):
# 构建命令前加环境变量 CARGO_NET_GIT_FETCH_WITH_CLI=true

关键点 4:fork CLI 与 ohrs

# 从 yangyongzhen/tauri 的 feat/open-harmony 分支构建安装 CLI(含 ohos 子命令)
cargo install --path <tauri-fork>/crates/tauri-cli
cargo install ohrs        # cargo tauri ohos build 内部调用的助手

cargo tauri ohos --help   # 出现 init/dev/build 子命令即成功

四、第一步:改造 Cargo.toml(最绕的一步)

这是整个移植里最需要理解的一步,我把完整配置贴出来并逐段解释。

[package]
name = "grove"
version = "1.2.0"

[lib]
name = "grove_lib"
# 桌面版 rlib + OHOS 需要 cdylib(产出 .so)
crate-type = ["staticlib", "cdylib", "rlib"]

[build-dependencies]
# 版本钉 =2.6.3:patch 按精确版本替换,^2.6 会被 crates.io 新版绕过
tauri-build = { version = "=2.6.3", features = [] }

[dependencies]
# custom-protocol:生产模式必需(见「坑 2」),protocol-asset 需要 conf 里开 assetProtocol
tauri = { version = "=2.11.5", features = ["custom-protocol", "protocol-asset", "image-png"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
redb = "2"          # 纯 Rust 嵌入式 KV,零 C 编译,鸿蒙上无痛
chrono = "0.4"
# 强制 wry 用 OHOS patch 版本:registry 已有 0.56.1,^0.56.0 会解析到它而绕过 git patch
wry = { version = "=0.56.0", default-features = false }

# OHOS:napi 桥接(mobile_entry_point 宏展开必需,缺了报 cannot find crate napi_ohos)
[target.'cfg(target_env = "ohos")'.dependencies]
napi-derive-ohos = "1.1"
napi-ohos = { version = "1.1", features = ["napi8"] }

[profile.release]
strip = true

# ---- OHOS fork 替换(yangyongzhen/tauri feat/open-harmony,官方基线 2.11.5)----
[patch.crates-io]
tauri = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-build = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-macros = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-codegen = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-utils = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-runtime = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
tauri-runtime-wry = { git = "https://github.com/yangyongzhen/tauri", rev = "e3bf6eb…" }
wry = { git = "https://github.com/tauri-apps/wry", rev = "6aaf4b84…" }
tao = { git = "https://github.com/tauri-apps/tao", rev = "813572fb…" }

[patch."https://github.com/harmony-contrib/openharmony-ability.git"]
openharmony-ability = { git = "https://github.com/yangyongzhen/openharmony-ability", rev = "295a276a…" }
openharmony-ability-derive = { git = "https://github.com/yangyongzhen/openharmony-ability", rev = "295a276a…" }

为什么用 [patch] 而不是 path 依赖?

cargo 解析器不会把 path 依赖当作其他 crate 的 tauri 版本候选links="Tauri"冲突),而 [patch] 是标准机制——声明 tauri = "=2.11.5" 后用 patch 把它精确替换成fork 的 git rev,整个依赖树(包括插件传递依赖)都会用到 fork 版本。必须钉 =x.y.z
精确版本
:patch 只替换「解析器选中的精确版本」,如果 crates.io 发了更高版本,^2.11 需求会解析到新版而绕过 patch(本机没有 OHOS 支持)。

为什么 wry/tao 也要 patch?

fork 的 tauri-runtime-wry 里声明的是 registry 版本(wry 0.56.0 / tao 0.36.0),而 OHOS 支持在 tauri-apps 官方仓库的分支里。fork 根 Cargo.toml 的 workspace patch 不跨工程继承,所以你的项目必须自己补 patch。而且 registry 上 wry 已发到 0.56.1,^0.56.0 会解析到更高的 0.56.1 绕过 git patch——所以还得在 [dependencies] 里显式钉 wry = "=0.56.0"

为什么 openharmony-ability 要单独 patch?

openharmony-ability 的 main 分支后来大重构成了 1.0.0-beta.1(webview 拆成独立插件crate),而 wry/tauri 的 OHOS 分支仍要求 0.3.0 的 webview feature。解法是把它patch 到用户 fork 的 feat/0.3.0 分支(= upstream commit 295a276)。注意 cargo
不允许 patch 指向同源不同 rev,所以必须用不同的 git URL

改完之后先跑一遍验证依赖能解析:

cd src-tauri
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo fetch
# 看到 Adding tauri v2.11.5 (...?rev=e3bf6eb...) / wry 0.56.0 / openharmony-ability 0.3.0 即成功

五、第二步:固定工具链与链接器

rust-toolchain.toml(src-tauri/ 下)

[toolchain]
channel = "stable-x86_64-pc-windows-gnullvm"
targets = ["aarch64-unknown-linux-ohos"]

这样 cargo tauri ohos build 内部调用的 cargo 也会用 gnullvm 工具链。

.cargo/config.toml(src-tauri/.cargo/ 下)

OHOS NDK 自带的 aarch64-unknown-linux-ohos-clang 是 Unix shell 脚本,Windows 跑
不了,直接用 NDK 的 clang.exe 做链接器:

[target.aarch64-unknown-linux-ohos]
linker = "D:\\oh\\DevEcoStudio\\sdk\\HarmonyOS-NEXT-DB6\\openharmony\\native\\llvm\\bin\\clang.exe"
ar = "D:\\oh\\DevEcoStudio\\sdk\\HarmonyOS-NEXT-DB6\\openharmony\\native\\llvm\\bin\\llvm-ar.exe"
rustflags = [
    "-C", "link-arg=-target",
    "-C", "link-arg=aarch64-linux-ohos",
    "-C", "link-arg=--sysroot=D:\\oh\\DevEcoStudio\\sdk\\HarmonyOS-NEXT-DB6\\openharmony\\native\\sysroot",
    "-C", "link-arg=-fuse-ld=lld",
    "-C", "link-arg=--rtlib=compiler-rt",
]

纯 Rust 依赖树(redb/chrono/serde)不需要 C 编译器;如果有 C 依赖(如 ring),
还要补 CC_aarch64_unknown_linux_ohos 等环境变量(cc crate 用)。


六、第三步:tauri.conf.json 与能力文件

tauri.conf.json 加两处

"app": {
  "withGlobalTauri": true,
  "security": {
    "csp": null,
    "assetProtocol": { "enable": true, "scope": [] }
  }
}
  • withGlobalTauri:ArkWeb 需要 window.__TAURI__ 全局桥;
  • assetProtocol:不配这个,protocol-asset feature 会被 tauri-build 拒绝(报错:The tauri dependency features on the Cargo.toml file does not match the allowlist defined under tauri.conf.json)。

capabilities/default.json(新建)

{
  "$schema": "../gen/schemas/desktop-schema.json",
  "identifier": "default",
  "description": "主窗口默认权限",
  "windows": ["main"],
  "permissions": ["core:default"]
}

我们的应用只用自定义命令 + getVersioncore:default 足够。


七、第四步:前端适配(两个必须处理的坑)

前端业务代码零改动,但 index.html 和 ui.ts 各补一个鸿蒙专属的适配。

坑 3:ArkWeb 自定义 scheme 下 localStorage 是 null

tauri://localhost 是自定义 scheme,ArkWeb 下 window.localStorage 为 null,任何读 localStorage 的代码都会崩(我们的 theme.ts 直接读写设置)。必须在任何ES module 执行之前装一个内存降级实现——放 index.html 内联 <script> 里,别放JS 模块里(ES module 的 import 是提升的,polyfill 会晚一步):

<script>
  // 鸿蒙 ArkWeb:自定义 scheme(tauri://localhost)下 window.localStorage 为 null。
  if (typeof window !== "undefined" && !window.localStorage) {
    var __lsMap = new Map();
    Object.defineProperty(window, "localStorage", {
      value: {
        getItem: function (k) { return __lsMap.has(String(k)) ? __lsMap.get(String(k)) : null; },
        setItem: function (k, v) { __lsMap.set(String(k), String(v)); },
        removeItem: function (k) { __lsMap.delete(String(k)); },
        clear: function () { __lsMap.clear(); },
        key: function (i) { return Array.from(__lsMap.keys())[i] ?? null; },
        get length() { return __lsMap.size; }
      },
      configurable: true, writable: true
    });
  }
</script>
<script type="module" src="/main.ts"></script>

代价:设置(主题/布局/彩蛋开关)只存在内存里,重启后恢复默认——数据不受影响。

坑 4:ArkWeb 没有原生 confirm / prompt

桥接层没接 onJsConfirm/onJsPrompt 回调,原生 confirm() 静默返回 false、prompt() 返回 null——删除、重命名、改色、清空这些功能会静默失效(按钮点了没反应),这比崩溃还难发现。我写了一个应用内弹窗(Promise 化,复用现有设计令牌)

替换全部 5 处调用:

function confirmDialog(message: string, okText = "确定", danger = false): Promise<boolean> {
  return openDialog({ title: "确认", message, okText, cancelText: "取消", danger }).then((v) => v !== null);
}
function promptDialog(label: string, value = "", placeholder = ""): Promise<string | null> {
  return openDialog({ title: label, okText: "确定", cancelText: "取消", inputValue: value, inputPlaceholder: placeholder });
}

配套 CSS(.dialog 遮罩 + 卡片,z-index 80 介于 sheet 70 和 toast 90 之间)。替换后桌面与鸿蒙行为一致,顺带把桌面版的体验也统一了。


八、第五步:生成 DevEco 工程 + 交叉编译 .so

# 环境变量指 SDK 根目录(不是 native/)
export OHOS_HOME="D:\oh\DevEcoStudio\sdk\HarmonyOS-NEXT-DB6\openharmony"
export OHOS_NDK_HOME="$OHOS_HOME"

cd src-tauri
cargo tauri ohos init --skip-targets-install   # 生成 gen/ohos/ DevEco 工程
cargo tauri ohos build -t aarch64              # 交叉编译 .so(首次全量约 5-10 分钟)

产物:target/aarch64-unknown-linux-ohos/release/libgrove_lib.so(自动复制到 gen/ohos/entry/libs/arm64-v8a/)。

验证是 aarch64 ELF:

file libgrove_lib.so   # ELF 64-bit LSB shared object, ARM aarch64, ... stripped

坑 5:Windows 上 HAP 装配必然失败(预期行为)

cargo tauri ohos build 走到最后一步会在 Windows 上报Failed to assemble HAP: 系统找不到指定的文件。 (os error 2)——cargo-mobile2 用CreateProcess 拉 .bat,Windows 上拉不起来。别慌.so 已经就位,手工装配见下一步。


九、第六步:手工装配 HAP(Windows 专属)

1. 净化 hvigorfile.ts

生成的 gen/ohos/entry/hvigorfile.ts 带着 cargo 钩子(dev-eco-studio-script),Windows 上一跑就炸。替换成无钩子版(.so 已手动编译):

import { hapTasks } from '@ohos/hvigor-ohos-plugin';

export default {
  system: hapTasks,
  plugins: []   /* cargo 钩子已移除:.so 由 cargo tauri ohos build 手动编译 */
}

根目录 gen/ohos/hvigorfile.ts 已经是干净的(plugins: []),不用动。

2. 同步前端 rawfile(ohos build 不会自动做!)

rm -rf gen/ohos/entry/src/main/resources/rawfile
mkdir -p gen/ohos/entry/src/main/resources/rawfile
cp -r dist/. gen/ohos/entry/src/main/resources/rawfile/

3. ohpm 装依赖 + hvigor 打包

cd gen/ohos
ohpm install && cd entry && ohpm install && cd ..

# 关键:PATH 必须是纯 Windows 风格(含 System32 + DevEco 的 node/jbr),
# hvigor 的 es2abc/Java 子进程要 cmd.exe 和 java,MSYS 的 PATH 转换会让 spawn(cmd.exe) ENOENT
node "D:/Program Files/Huawei/DevEco Studio/tools/hvigor/bin/hvigorw.js" \
  assembleHap --mode module -p product=default --no-daemon

产物:entry/build/default/outputs/default/entry-default-signed.hap(有签名配置时是 signed,否则 unsigned)。

4. 替换应用图标(可选但推荐)

模板生成的图标是 DevEco 默认蓝色,把 AppScope/resources/base/media/entry/src/main/resources/base/media/ 下的 foreground.png(1024×1024 RGBA 应用logo)、background.png(主题色)、startIcon.png 换成自己的。


十、第七步:签名(需要你登录华为账号)

签名材料是按 bundle 绑定的。我用 openssl 把本机 ~/.ohos/config/ 下全部 15 套现成材料解码了一遍,确认每个 profile 的 bundle-name 都指向别的应用(DevBox、m3u8dl、cc-switch……),没有一套属于 com.grove.habittrees——必须新建。

这是全流程唯一必须人工完成的步骤:

  1. DevEco Studio → Open → 打开 src-tauri/gen/ohos
  2. File → Project Structure → Signing Configs → 勾选 Automatically generate signature(未登录会弹华为账号登录);
  3. 材料自动写入 build-profile.json5 + ~/.ohos/config/

之后命令行 hvigor 就能直接复用,不需要 GUI。

小技巧:cargo tauri ohos init 重新生成工程会清空 signingConfigs,如果材料还在
~/.ohos/config/ 里,把 build-profile.json5 的 material 段恢复回去即可,不用再登录。
前提是 bundle 没变。


十一、第八步:真机部署与验证

hdc install -r entry-default-signed.hap          # 在产物目录(hdc 传相对路径最稳)
hdc shell "aa start -a EntryAbility -b com.grove.habittrees"

# 验证三件套
hdc shell "ps -ef | grep habittrees"             # 主进程 + :render 进程存活
devecocli log --crash --bundle-name com.grove.habittrees   # 无 panic/fatal
hdc shell "snapshot_display -f /data/local/tmp/s.jpeg"     # 截图
hdc file recv /data/local/tmp/s.jpeg s.jpeg

数据目录:/data/app/el2/100/base/com.grove.habittrees/files/grove.redb

功能链路实测:用 uinput 模拟触控(uinput -T -d <x> <y> -i 80 -u <x> <y>)点 FAB 种树、输入名称、点「种下种子」,然后 strings grove.redb 确认数据落库——前端 invoke → Rust → redb → 前端刷新,全链路打通。

坑 6:屏幕锁屏装不上

aa startError Code:10106102 ... The device screen is locked——开发者模式下系统不会自动解锁,power-shell wakeup + uinput 上滑也解不开(有 PIN 的话)。这是唯一需要用户手动配合的:解锁手机并保持亮屏。

坑 7:hdc 绝对路径拼接错误

hdc file recv /xxx "E:\绝对\路径" 会拼成 E:\test\E:/test/... 找不到文件。用相对路径:先 cd 到目标目录再传相对文件名。


十二、踩坑全记录:两个差点劝退我的大坑

下面这两个坑是我这次移植花费 80% 时间的部分,完整记录定位过程,供后人参考。

坑 1(致命):启动即崩溃 SIGABRT —— 根因是 app_data_dir()

现象hdc install 成功,aa start 报成功,1 秒后进程消失,无任何界面。

定位过程(按顺序):

  1. devecocli log --crash 只看到
    DFX_SignalHandler :: signo(6) ... processName(com.grove.habittrees) —— SIGABRT,主线程自杀。faultlog 目录/data/log/faultlog/faultlogger/ 权限不足(无 su),
    hdc file recv 可以把整个目录拉回来,拿到完整 cppcrash 日志;
  2. 崩溃栈显示 abort 直接发生在 libgrove_lib.so 内部:
    #00 raise → #01 abort → #02..#12 libgrove_lib.so → #16 XComponentPattern::OnSurfaceCreated。而且 LastFatalMessage: [OnSurfaceCreated] crash occured on callback
  3. 用 NDK 的 llvm-addr2line 反查帧地址,但第一次反查结果全是 alloc/mio/tokio 的碎片符号——因为 .so 被我 strip 了且地址布局对不上。重新用CARGO_PROFILE_RELEASE_DEBUG=2 CARGO_PROFILE_RELEASE_STRIP=false 编译带调试信息的 .so,装到手机复现,用同一份 .so 反查才拿到准确的调用链;
  4. 真相:
    #09 core::panicking::panic_fmt
    #10 tauri::app::App::make_run_event_loop_callback ... app.rs:1425
    
    打开 fork 源码 app.rs:1425:
    RuntimeRunEvent::Ready => {
      if let Err(e) = setup(&mut self) {
        panic!("Failed to setup app: {e}");   // ← 就是这里
      }
    
  5. 根因:OHOS 的 Rust target target_os = "linux",Tauri 走桌面路径解析器;而鸿蒙系统没有 HOME 环境变量dirs::data_dir() 返回 None →app.path().app_data_dir()UnknownPath → setup 返回 Err → panic。而且 OHOS 目标默认 panic 后直接 abort(不 unwind)。

修复(lib.rs):

fn resolve_app_data_dir(app: &tauri::App) -> Result<PathBuf, tauri::Error> {
    #[cfg(target_env = "ohos")]
    {
        // 应用沙箱 filesDir,/data/storage/el2/base/files 与
        // /data/app/el2/100/base/<bundle>/files 是同一目录的两个挂载视图
        Ok(PathBuf::from("/data/storage/el2/base/files"))
    }
    #[cfg(not(target_env = "ohos"))]
    {
        app.path().app_data_dir()
    }
}

参考:cc-switch 项目里的 get_home_dir() 用了一模一样的方案——他们早就踩过这个坑了。
教训:setup 里别用 app_data_dir(),直接按平台给路径。

诊断小工具(保留在代码里,以后崩溃能直接看原因):panic hook 把消息 + backtrace 写到应用数据目录的 panic.log

std::panic::set_hook(Box::new(move |info| {
    let bt = std::backtrace::Backtrace::force_capture();
    let msg = format!("[panic] {info}\n{bt}\n----\n");
    let _ = std::fs::OpenOptions::new().create(true).append(true)
        .open(&log_path).and_then(|mut f| f.write_all(msg.as_bytes()));
}));

坑 2(迷惑):白屏 + “failed to request localhost:5173” —— 根因是 dev 模式

现象:进程活着、页面加载日志正常(PageLoadFinished url:tauri://localhost/),但屏幕上显示 failed to request localhost:5173,截图分析 99% 纯白。

定位过程

  1. 项目里搜 “5173” 只有 tauri.conf.jsondevUrl —— 说明运行时在用开发服务器地址,而不是打包好的前端;
  2. 铁证:strings libgrove_lib.so | grep 5173 命中了内嵌的http://localhost:5173 —— devUrl 被编进了产物;
  3. 读 fork 源码 tauri/build.rs
    let custom_protocol = has_feature("custom-protocol");
    let dev = !custom_protocol;
    
    dev = !custom_protocol!release 构建必须带 custom-protocol feature,否则就是 dev 模式,运行时去加载 devUrl;
  4. 查 CLI 源码,tauri build/ohos buildbuild_options 确实会features.push("tauri/custom-protocol"),但我们的产物没吃到——保险起见直接在Cargo.toml 里显式加上;
  5. custom-protocol 后又报 allowlist 校验错误——protocol-asset feature 需要conf 里开 assetProtocol,一起补上。

修复

tauri = { version = "=2.11.5", features = ["custom-protocol", "protocol-asset", "image-png"] }
"security": { "assetProtocol": { "enable": true, "scope": [] } }

教训:产物验证时不要只信进程存活和页面加载日志,要看屏幕。 我也是靠截图像素分析(PIL 统计非白比例、找主题绿按钮)才发现是白屏的。

坑 3-7(小坑速记)

  • hilog 缓冲滚动快:崩溃日志 10 秒内就被冲掉,要在启动后立刻 hilog -x | grep 抓取,或者干脆拉 faultlog 文件;
  • hvigor 需要纯 Windows PATHC:\Windows\System32 + DevEco 的 nodejbr\bin必须在 PATH 里,否则 spawn(cmd.exe) ENOENT;
  • DevEco 打开项目会顺手 build:它 sync 时会编译(还能顺带帮你签名), hvigor 任务显示 UP-TO-DATE 别慌,产物是对的;
  • 编译警告:OHOS 分支下 app 参数未使用之类的警告,加 #[cfg_attr(target_env = "ohos", allow(unused_variables))] 消掉;
  • 签名不一致:换过签名材料后报 error:install sign info inconsistent,先 hdc uninstall 再装。

十三、坑位速查表

# 现象 解法
1 app_data_dir() 在 OHOS 报错 启动 1 秒 SIGABRT,无界面 setup 里按 cfg(target_env="ohos") 直接用 /data/storage/el2/base/files
2 产物是 dev 模式 白屏 / failed to request localhost:5173 Cargo.toml 加 custom-protocol feature + conf 开 assetProtocol
3 ArkWeb localStorage 为 null 读写设置崩溃/报错 index.html 内联内存 polyfill(必须在 module 之前)
4 无原生 confirm/prompt 删除/重命名静默失效 应用内 Promise 弹窗替换
5 Windows HAP 装配失败 Failed to assemble HAP: os error 2 预期行为,手工 ohpm + hvigor 装配
6 屏幕锁屏 aa start 10106102 用户解锁(开发者模式不自动解锁)
7 hdc 路径拼接 绝对路径 recv 报错 用相对路径
8 签名按 bundle 绑定 现成材料不能复用 DevEco 登录华为账号自动签名
9 hvigorfile cargo 钩子 Windows 上跑炸 净化成 plugins: []
10 崩溃栈反查失真 addr2line 出碎片符号 用同一份未 strip 的 .so 反查
11 rawfile 不同步 页面 404/白屏 cargo tauri ohos build 后手动 cp dist/. rawfile/
12 hvigor 找不到 cmd.exe/java ENOENT 纯 Windows PATH(System32 + DevEco node/jbr)

十四、验证清单与已知限制

验收清单(本次全部通过)

  • cargo tauri ohos build 产出 .sofile 确认 aarch64 ELF
  • entry-default-signed.hap(7.2MB,签名版)
  • 真机 nova 14(API 24)安装启动,主进程 + render 进程存活
  • hilog 无 panic / fatal,panic.log 不存在
  • 截图确认 UI 渲染(主题绿、FAB、树卡片)
  • 功能链路:种树 → invoke → Rust → redb → 前端刷新(含今日自动打卡)
  • 重装(升级)后数据保留

已知限制

  1. 设置项不持久:主题/布局/彩蛋开关走 localStorage,ArkWeb 自定义 scheme 下是 null,内存 polyfill 只保本次运行——重启恢复默认(数据不受影响)。后续可加一个 Rust 命令把设置存进 redb 解决;
  2. 备份导出 / 彩蛋卡片保存用 Blob 下载,ArkWeb 的 onDownloadStart 桥接未接线,鸿蒙上可能没反应(桌面正常)。可加导出弹窗 + 复制按钮回退;
  3. 桌面版行为不变(数据仍在 %APPDATA%\com.grove.habittrees\grove.redb)。

十五、重建流程速查

改代码后完整重建链路:

# 1) 前端(改过 src/ 才需要)
npm run build

# 2) 交叉编译(改过 Rust 才需要;OHOS_HOME/OHOS_NDK_HOME 指向 SDK 根目录)
cd src-tauri
CARGO_NET_GIT_FETCH_WITH_CLI=true cargo build -p grove --release --target aarch64-unknown-linux-ohos
cp target/aarch64-unknown-linux-ohos/release/libgrove_lib.so gen/ohos/entry/libs/arm64-v8a/

# 3) rawfile 同步(改过前端才需要)
rm -rf gen/ohos/entry/src/main/resources/rawfile && mkdir -p gen/ohos/entry/src/main/resources/rawfile
cp -r ../dist/. gen/ohos/entry/src/main/resources/rawfile/

# 4) 打包 + 部署
cd gen/ohos
node "D:/Program Files/Huawei/DevEco Studio/tools/hvigor/bin/hvigorw.js" \
  assembleHap --mode module -p product=default --no-daemon
hdc install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc shell "aa start -a EntryAbility -b com.grove.habittrees"

参考

  • 官方 OHOS 分支:yangyongzhen/tauri(feat/open-harmony,tauri 2.11.5)、
    tauri-apps/wrytauri-apps/tao 的 feat/open-harmony 分支
  • 同机型实测参考:cc-switch-harmonyos/doc/cc-switch-鸿蒙PC移植实录-官方fork版.md
  • 社区旧 fork 方案:m3u8dl-tauri/docs/TAURI_TO_HARMONYOS_PORTING_GUIDE.md
Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐