本文记录 RouteComposer 接入 OpenHarmony 的完整过程,覆盖 iOS 路由库盘点、ArkTS 源码级移植决策、六大实体与路由引擎移植、UIKit 到 ArkUI 的平台映射、ArkUI 真机页面、HAP 构建、签名和真机验收。

RouteComposer 是与具体业务无关的 iOS 导航/深链库(纯 Swift、协议导向)。它与本系列前序的 Kotlin/CMP 库不同:Swift 没有 Kotlin/Native 或 N-API 通道可走,因此本次适配采用源码级 ArkTS 移植——把库的核心抽象(Router、Step 链、Finder、Factory、Action、Interceptor、ContextTask、PostRoutingTask 与装配 DSL)逐语义移植为 ArkTS,平台层映射到 ArkUI 的 Navigation/NavPathStack/Tabs,ArkUI 页面作为真机验收宿主。

项目地址: AtomGit/oh-tpc/route-composer

上游仓库: ekazaev/RouteComposer

开发工具: 华为云码道

环境安装: 环境安装指南

一、背景

1.1 为什么做 OpenHarmony 平台适配

RouteComposer 为 iOS 提供 viewController 的组合(composition)、导航(navigation)与深链(deep linking):把一次导航拆成可复用的小步骤(Finder 找已有屏、Factory 建新屏、Action 集成进栈),声明式配置路由表,把导航代码从页面中移走,每个页面天然支持深链。上游 2.22.2 共 151 个 Swift 文件、约 9800 行,纯逻辑 + UIKit 集成层。

OpenHarmony 应用同样需要这套「协议式路由」能力(页面解耦、深链直达、登录拦截、Tab 容器编排),但上游只有 Swift 实现,不存在任何跨语言编译通道:

障碍具体问题
语言不可编译Swift 源码无法编译进 OpenHarmony,没有 Kotlin/Native 或 N-API 的等价通道。
框架体系不同上游深度绑定 UIViewController 层级遍历,ArkUI 是声明式渲染,无可内省的 VC 树。
容器语义不同UINavigationController/UITabBarController/模态链与 NavPathStack/Tabs/覆盖层一一错位。
类型系统差异Swift 协议 + associatedtype + 类型擦除体系需要映射到 ArkTS 接口(禁 any、禁重载、禁结构类型)。
DSL 语法差异上游 adding(...) 依赖重载、方法带参数标签,ArkTS 均不允许。
交付链路复杂HAR 库模块、HAP 工程、签名和 hdc 都要单独验收。

因此,本项目把边界放在「核心语义逐行对照移植 + 平台映射层」:路由算法、步骤链、实体协议照搬上游语义;UIKit 依赖被映射为显式注册的「逻辑屏幕树」,ArkTS 只负责渲染这棵树。

1.2 库提供的能力

Demo 覆盖九个可观察场景(与上游 iOS Example 一一对应):

场景装配方式上游对应
Welcome → 主界面CompleteFactoryAssembly + TabsReplaceRootAction 整体替换根homeScreen(TabBar replaceRoot)
三 Tab 容器每_tab 一个 Navigation(NavPathStack) 栈UITabBarController + 子导航栈
Tab 内 PushStepAssembly + PushActionfiguresAndProductScreen
替换栈顶PushReplacingLastActioncolorScreen
有栈就 Push、无栈就模态SwitchAssembly.addCase(NavHostTopFinder)Product 的三分支装配
登录拦截LoginInterceptor + 排队导航 + 登录后续航LoginConfiguration
动态加 TabAddTabAction 容器动作starScreen 的 addTab 装配
复用已有屏ClassFinder 命中不重复入栈,仅切 TabcircleScreen(classFinder)
模态叠模态PresentAction 作用于模态层secondModalScreen

核心数据结构 RCScreen(逻辑屏幕节点)包含:

字段作用
name屏幕类型标识(对应上游 UIViewController 类型)。
paramsRecord<string, Object> 上下文载荷(对应上游 Context 注入)。

1.3 实现适配

维度要求
代码复用路由引擎、步骤链、六大实体、装配 DSL 全部按上游语义移植,方法名与注释标注上游出处。
平台目标纯 ArkTS HAR 库模块,无 .so、无 NAPI、无 abiFilters 依赖。
映射显式UIKit 容器全部映射到注册屏幕树(RootHostState/TabsHostState/NavHostState/ModalHostState)。
Demo 完整九个场景 + 全局埋点日志抽屉 + 防重入 + 登录拦截续航。
仓库规范项目说明、文章、效果图和源码链接统一使用 AtomGit。

本项目的 ArkUI 页面不是对上游 UI 的像素级复刻,而是路由模型的行为验收宿主:每个按钮触发一次路由表装配的导航,日志抽屉逐条打印引擎的决策过程。

二、实现路线图

第 1 阶段:项目初始化     ── 盘点源集、查重、确定源码级移植路线
第 2 阶段:工程打通       ── HAR 库模块 + HAP Demo 工程、依赖与构建链
第 3 阶段:核心移植       ── 六大实体、步骤链、DefaultRouter 三阶段算法
第 4 阶段:平台映射层     ── 逻辑屏幕树、宿主注册、九个 Action 的落地
第 5 阶段:ArkUI 宿主     ── 单页渲染注册屏幕树、@Builder 屏幕目录、日志抽屉
第 6 阶段:示例与验证     ── Node 复现测试、HAP、签名、真机运行和效果图

三、逐步实现过程

第 1 阶段:项目初始化

1.1 工程边界
RouteComposer/                  上游 Swift 源码(151 文件)原样保留,不参与鸿蒙构建
Example/RouteComposer/          上游 iOS Example 原样保留(对照用)
Example/harmonyApp/             DevEco 工程:routecomposer HAR 库 + entry Demo
Example/harmonyApp/routecomposer/   ArkTS 移植本体(八个子模块)
README.md / README.upstream.md / README.OpenHarmony.md / README.OpenHarmony_CN.md

上游已有大写 Example/ 目录(iOS Demo),macOS 文件系统大小写不敏感,鸿蒙 Demo 放在 Example/harmonyApp,与已过审仓库的 example/harmonyApp 同一规则,路径大小写随上游目录。README 四件套:中文首页、上游原文(git mv 保留)、英文摘要、中文主文档(22 节、6700+ 汉字)。

1.2 工具链矩阵
项目配置
移植语言ArkTS(HAR 静态共享库)
DevEco Studio26.0.0.821
hvigor modelVersion5.1.0
compatibleSdkVersion / targetSdkVersion5.1.0(18) / 5.1.0(18),runtimeOS HarmonyOS
ABI纯 ArkTS,无 native 依赖,无需 abiFilters
Bundleorg.terminator.ohos.routecomposer
版本HAR 2.22.2(SemVer),App versionName 2.22.2-ohos.1
1.3 页面边界
标题区          RouteComposer · OHOS + 登录状态 + 登录/登出 + 日志开关
根内容区        welcome 单屏 或 三 Tab 容器(每 Tab 一个 Navigation 栈)
模态层          登录卡 / Wishlist 卡 / Second Modal 卡(支持叠层)
日志抽屉        底部 40% 面板,逐条显示路由埋点与拦截日志

页面启动时 bootstrap() 把 welcome 设为根屏;「进入主界面」触发 TabsReplaceRootAction,Tabs 宿主由路由动作在首次替换根时创建,不需要预注册。

第 2 阶段:工程打通

2.1 HAR 库模块
Example/harmonyApp/
├── routecomposer/            HAR 库模块(harTasks)
│   ├── Index.ets             公开 API 桶文件(约 60 个导出)
│   ├── oh-package.json5      name: routecomposer, version: 2.22.2(SemVer)
│   ├── build-profile.json5   apiType stageMode
│   ├── hvigorfile.ts         harTasks
│   └── src/main/ets/routecomposer/
│       ├── core/             RCTypes / Contracts / Hosts(值类型、六实体接口与 Box、屏幕树)
│       ├── steps/            Steps(RoutingStep 链、BaseStep、DestinationStep、GeneralStep)
│       ├── router/           DefaultRouter(三阶段导航引擎)
│       ├── assemblies/       StepAssembly / ChainAssembly / SwitchAssembly / CompleteFactoryAssembly
│       ├── finders/          BaseFinders / Finders(ClassFinder 等 + 栈迭代器)
│       ├── factories/        Factories(ScreenFactory / TabsContainerFactory)
│       ├── actions/          Actions(九个 Action 的平台落地)
│       └── extra/            Extras(Inline* / Destination / 装饰器 Router)
├── entry/                    HAP Demo(hapTasks,依赖 file:../routecomposer)
└── build-profile.json5       modules: [entry, routecomposer]

HAR 模块要点:src/main/module.json5 声明 "type": "har";oh-package.json5 的 version 必须是严格 SemVer(2.22.2-ohos.1 会被 ohpm 拒绝,-ohos.1 后缀保留在 App 的 versionName 里,与本系列已过审仓库一致)。

2.2 构建验证
cd Example/harmonyApp
ohpm install
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon
# > hvigor BUILD SUCCESSFUL

命令行验证用 ~/ohos/command-line-tools(hvigorw + ohpm 6.0.1),真机构建在 DevEco Studio 26 内完成。两套工具链不要混用缓存:切换前删除项目内 .hvigor/、oh_modules/ 与 lock 文件,否则 DevEco 侧会报 (0, hvigor_1.WithParamReplacement) is not a function 这类内部函数缺失错误(详见 8.1 踩坑复盘)。

第 3 阶段:核心移植

3.1 UIKit → ArkUI 映射契约
上游 UIViewController 层级         本仓逻辑屏幕树(ScreenHostRegistry)
UIWindow                 →        RootHostState(页面根内容)
UINavigationController   →        NavHostState(NavPathStack + 镜像数组)
UITabBarController       →        TabsHostState(N 个 NavHostState + 选中索引)
presented 模态链          →        ModalHostState(全屏覆盖层链)
UIViewController          →        RCScreen(name + params,由页面 @Builder 渲染)

页面只是这棵树的渲染器:根内容 + Tabs(ForEach TabContent → Navigation(stack)) + 模态覆盖卡片。库是树的唯一写者,页面通过 RCNotifier 订阅变更刷新 @State。

3.2 六大实体与类型擦除

Swift 协议 + Any* + Box 体系天然适配 ArkTS 的「接口 + 包装类」:

export interface Finder<C> {
  prepare(context: C): void;
  findViewController(context: C): ScreenRef | undefined;
}
export interface Factory<C> {
  prepare(context: C): void;
  build(context: C): RCScreen;
}
export interface Action {
  perform(screen: RCScreen, previous: ScreenRef, animated: boolean,
    completion: (result: RoutingResult, newRef: ScreenRef | undefined) => void): void;
}
export interface RoutingInterceptor<C> {
  perform(context: C, completion: (result: RoutingResult) => void): void;
}

步骤内以擦除形态存异构实体(对应上游 Router/Type Erasure/ 8 个 Any* + 12 个 Box):

export class FinderBox<C> implements AnyFinder {
  private readonly finder: Finder<C>;
  findViewController(context: RCContext): ScreenRef | undefined {
    return this.finder.findViewController(context as C);   // 泛型运行时擦除
  }
}

Swift 的 adding(...) 重载在 ArkTS 拆为三个具名方法:addingInterceptor / addingContextTask / addingPostRoutingTask。

3.3 DefaultRouter 三阶段算法

对照上游 DefaultRouter.swift 逐段移植 navigate:

第 1 阶段 prepareFactoriesStack
  toContext 装箱 → 沿步骤链(destination 最先)逐个 perform
  SuccessResult   finder 命中 → 导航起点(先跑本步 ContextTask)
  BuildResult     未命中有工厂 → 工厂载荷 unshift 进 buildList(根方向在前)
  UpdateContext   ConvertingStep 更新上下文继续走
  全部未命中 → RoutingError.initialControllerNotFound

第 2 阶段 startNavigation
  拦截器链顺序异步执行(递归 index runner),任一失败即终止
  dismiss 起点之上的模态(上游 dismissPresented 语义)

第 3 阶段 buildViewControllerStack
  从容器到叶子递归:makeVisible 切 Tab → factory.build → ContextTask
  → action.perform 集成(push/present/replaceRoot/...)→ 下一工厂
  工厂耗尽 → 全部 PostRoutingTask → completion(success)

每阶段都有 RCLoggerHolder 日志,真机「日志」抽屉可直接看到引擎决策。

3.4 装配 DSL 移植
上游本仓 ArkTS
StepAssembly(finder:factory:).using(action).adding(...).from(prev).assemble()StepAssembly.finder(f, fa).using(action).addingInterceptor(...).from(prev).assemble()
ChainAssembly.from(step)ChainAssembly.from<RCContext>(step).using(action).assemble()
SwitchAssembly.addCase(expecting:).assemble(default:)new SwitchAssembly<RCContext>().addCase(finder, step).assembleUsingDefault(default)
CompleteFactoryAssembly(factory:).with(f).using(.add())...CompleteFactoryAssembly.with(f, addTab).with(...).usingRoot(tabsReplaceRoot).from(GeneralStep.root()).assemble()

容器装配必须 .from(GeneralStep.root()) 锚定起点,否则路由器找不到导航起点(这是真机首验发现的问题之一,见 8.1)。

第 4 阶段:平台映射层(技术难点)

4.1 逻辑屏幕树与镜像数组

ArkUI 的 NavPathStack 没有树状遍历 API(只有 getAllPathName() / getParamByName()),而 Finder 的本职就是「在栈里找屏」。解法是库侧镜像:

export class NavHostState {
  readonly stack: NavPathStack;   // ArkUI 渲染驱动
  screens: RCScreen[];            // 库侧镜像,Finder 的搜索对象
  push(screen: RCScreen, animated: boolean): void {
    this.stack.pushPathByName(screen.name, screen.params, animated);
    this.screens.push(screen);
  }
  syncFromStack(): void {         // 每次搜索前重建镜像,吸收手势返回造成的脱同步
    const names: string[] = this.stack.getAllPathName() as string[];
    // 名单一致则跳过;否则按 name + getParamByName 重建
  }
}

DefaultStackIterator 的遍历顺序对应上游 .fullStack:模态层(顶先)→ 当前 Tab 宿主 → 其他 Tab → 独立宿主 → 根内容,起点前先对全部宿主 syncFromStack()。

4.2 九个 Action 的落地
本仓 Action上游对应落地方式
PushActionNavigationControllerAction.pushprevious.host.push(screen)
PushAsRootAction.pushAsRoot(setViewControllers)host.replaceAll(screen)
PushReplacingLastAction.pushReplacingLasthost.popAndPush(screen)
PresentActionGeneralAction.presentModallyregistry.modal.present(screen)
ReplaceRootActionGeneralAction.replaceRootregistry.root.setContent(screen)
NilActionGeneralAction.nilAction什么都不做,直接成功
DelayedActionDispatchQueue.delay 包装setTimeout 延迟整个 perform
TabsReplaceRootActionTabBarControllerFactory + replaceRoot每个 TabSeed 建 NavHostState + seed → registry.setTabs(...)
AddTabActionUITabBarController.add()取 embedding.tabs[0] → currentTabs.addTab(host, true)
4.3 两个装饰器 Router
  • GlobalInterceptorRouter(上游同名):全局拦截器在委托前顺序执行,全局 PostTask 在成功后执行;
  • SingleNavigationRouter(上游同名):排队语义——导航进行中的新导航不拒绝而是 postpone,当前导航完成后补跑。这正是登录拦截器的关键依赖:拦截器在「被拦截的那次导航」内部发起 navigate(loginScreen),排队机制保证它在上一次导航收尾后立刻执行。

第 5 阶段:ArkUI 宿主

5.1 页面 = 注册屏幕树的渲染器
build() {
  Stack({ alignContent: Alignment.TopStart }) {
    if (this.rootIsTabs) {
      this.TabsRoot(this.registry.currentTabs as TabsHostState)   // Tabs + N 个 Navigation
    } else {
      this.WelcomeScreen()
    }
    if (this.modalCount > 0) { this.ModalLayer() }                // 最顶模态 + 半透明遮罩
    this.TopBar()                                                 // 登录态 + 日志开关
    if (this.logOpen) { this.LogDrawer() }
  }
}
5.2 屏幕目录与状态桥接
@Builder
ScreenDestination(name: string, param?: Object) {
  if (name === 'circle') { this.CirclePage() }
  else if (name === 'product') { this.ProductPage(param) }        // 显示 params.id
  // ... square / home / star / color / 兜底
}

ArkUI 是细粒度更新,库对屏幕树的每次写入经 RCNotifier 回调搬进页面 @State(rootIsTabs / tabHosts / modalCount / topModal / logLines),再由 @State 驱动刷新——对应 UIKit「改了 VC 树系统自动排版」的隐式行为。

5.3 登录拦截续航
export class LoginInterceptor implements RoutingInterceptor<RCContext> {
  perform(context: RCContext, completion: (r: RoutingResult) => void): void {
    if (DemoSession.shared().loggedIn) {
      completion(RoutingResult.ok());                             // 放行
    } else {
      DemoConfiguration.router().navigate(DemoConfiguration.loginScreen(), context); // 排队弹登录
      completion(RoutingResult.fail(RoutingError.intercepted('login required')));    // 本次失败
    }
  }
}

登录卡「登录」按钮:session.login() → modal.dismissTop() → 重新 navigate(wishlistScreen),此时拦截器放行、Wishlist 呈现——与上游 LoginConfiguration 的「拦截 → 登录 → 续航」流程一致。

第 6 阶段:示例与验证

6.1 工程结构
Example/harmonyApp/
├── routecomposer/src/main/ets/routecomposer/{core,steps,router,assemblies,finders,factories,actions,extra}/
├── entry/src/main/ets/demo/DemoState.ets          会话单例(登录态 + 日志)
├── entry/src/main/ets/demo/DemoConfiguration.ets  路由表(九个场景装配)
└── entry/src/main/ets/pages/Index.ets             单页宿主(树渲染器)
6.2 构建、签名和安装
# 命令行验证(无签名 debug 包)
cd Example/harmonyApp
ohpm install
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon

# 真机:DevEco Studio 打开 Example/harmonyApp(不要打开仓库根目录)
# File → Project Structure → Signing Configs → Automatically generate signature
hdc list targets -v
hdc -t <设备序列号> install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start -a EntryAbility -b org.terminator.ohos.routecomposer -m entry

四、完整代码对照

4.1 整体架构

DemoConfiguration.ets(路由表装配)
    │ StepAssembly / SwitchAssembly / CompleteFactoryAssembly
    ▼
DestinationStep 链 → DefaultRouter(三阶段引擎)
    │ Finder 搜索逻辑屏幕树 / Factory 产出 RCScreen / Action 集成入树
    ▼
ScreenHostRegistry(RootHostState + TabsHostState + NavHostState + ModalHostState)
    │ RCNotifier 变更通知
    ▼
Index.ets(@Builder 屏幕目录 + @State 桥接)
    ▼
ArkUI 真机页面(Tabs + Navigation + 模态覆盖层)

4.2 文件清单

文件职责
routecomposer/src/.../core/RCTypes.etsRCScreen / ScreenRef / RoutingResult / RoutingError / 日志与通知
routecomposer/src/.../core/Contracts.ets六实体接口 + Any*/Box 类型擦除 + ChildCoordinator
routecomposer/src/.../core/Hosts.ets逻辑屏幕树四类宿主 + 注册表 + 镜像同步
routecomposer/src/.../steps/Steps.etsRoutingStep 链 / BaseStep / SwitcherStep / DestinationStep / GeneralStep
routecomposer/src/.../router/DefaultRouter.ets三阶段导航引擎(对照 DefaultRouter.swift)
routecomposer/src/.../assemblies/Assemblies.ets四种装配 DSL(对照 Assemblies/ 目录)
routecomposer/src/.../finders/*.etsClassFinder 等 + StackIterator + SearchOptions 位掩码
routecomposer/src/.../actions/Actions.ets九个 Action 平台落地
routecomposer/src/.../extra/Extras.etsInline* / Destination / 两个装饰器 Router
entry/src/main/ets/demo/DemoConfiguration.ets九场景路由表 + LoginInterceptor
entry/src/main/ets/pages/Index.ets单页宿主(树渲染器 + 日志抽屉)
README.OpenHarmony_CN.md中文主文档(22 节:映射总表、踩坑表、故障排查、已知限制)

4.3 关键 API 对照

层次API作用
库Router.navigate(step, context, animated, completion)一次导航入口(上游 Router 协议)
库BaseStep.perform(context)步骤四态:命中 / 构建 / 更新上下文 / 无
库DefaultStackIterator.firstViewController(predicate, options)全树搜索(上游 StackIterator)
库TabsContainerFactory.build(context, coordinator)构建 Tab 容器种子栈
库GlobalInterceptorRouter / SingleNavigationRouter全局拦截 / 防重入排队
DemoDemoConfiguration.mainScreen()三 Tab 容器替换根
DemoDemoConfiguration.productScreen()SwitchAssembly 双分支
DemoLoginInterceptor.perform登录拦截 + 排队弹登录 + 失败返回

4.4 ArkTS 与上游 Swift 的边界

// ArkTS:步骤装配(上游 StepAssembly + adding 重载 → 具名方法)
StepAssembly.finder(new ClassFinder<RCContext>('wishlist'),
  new ScreenFactory<RCContext>('wishlist'))
  .using(new PresentAction())
  .addingInterceptor(new LoginInterceptor())
  .from(GeneralStep.current<RCContext>())
  .assemble();
// 上游 Swift:同一语义
StepAssembly(finder: ClassFinder(), factory: ClassFactory())
  .using(GeneralAction.presentModally())
  .adding(LoginInterceptor())
  .from(GeneralStep.current())
  .assemble()

两侧之间共享的是语义(链式步骤、实体分工、三阶段引擎),不共享源码——这是 Swift 源库与本系列 Kotlin 库适配路径的根本差异。

五、关键决策说明

决策 1:源码级 ArkTS 移植,而不是桥接

Swift 没有 OpenHarmony 编译通道(无 Kotlin/Native、无 N-API 等价物),唯一可行路线就是把核心抽象移植成 ArkTS。本仓是该系列里第一个 Swift 源库案例,验证了「KMP/KN 桥接」之外的另一条标准路径。

决策 2:显式逻辑屏幕树代替 VC 层级遍历

上游靠 UIWindow/UIViewController 的运行时层级做 Finder;ArkUI 声明式渲染不可内省。本仓让库拥有显式的注册屏幕树(页面创建 NavPathStack 等句柄注册进去),树是模型、ArkUI 是渲染——Finder/Action 的语义得以原样保留。

决策 3:照搬 Any* + Box 类型擦除体系

Swift 泛型与 TS 泛型都在运行时擦除,上游 8 个 Any* + 12 个 Box 的设计在 ArkTS 里几乎原样成立,异构实体集合(一个步骤里同时存 Finder/Factory/任务)无需任何发明。

决策 4:保持 completion 回调风格

上游拦截器/Action 是异步回调语义(拦截器可以弹 UI 再取消),强行 async/await 化会改变时序。本仓保持回调风格,SingleNavigationRouter 采用上游的 postpone(排队)语义,保证「拦截中再导航」可用。

决策 5:Node 复现环境做逻辑回归

把库(纯 TS 逻辑)与 Demo 路由表放进 Node,桩掉 NavPathStack,用 esbuild 打包跑「真机点击序列」。登录流程的三处缺陷(见 8.1)全部由该环境定位并验证修复,比真机盲试快一个数量级。

六、测试与验证

6.1 测试环境

本次验证使用 macOS(Apple Silicon)、DevEco Studio 26.0.0.821、command-line-tools 6.0.1(hvigorw + ohpm)、Node 24 + esbuild 复现环境;真机通过 DevEco 自动签名安装运行。

6.2 Node 复现测试

# 把 routecomposer HAR 源码 + DemoConfiguration/DemoState 拷入临时目录(.ets → .ts)
# 桩掉 NavPathStack(pushPathByName/pop/clear/getAllPathName/getParamByName)
npx esbuild harness.ts --bundle --platform=node --outfile=out.js && node out.js

复现脚本按真机点击序列执行五步:进入主界面 → 未登录点 Wishlist → 登录卡开着再点一次 → 登录按钮 → Second Modal。修复后的断言输出:

STATE[进入主界面]     modal=[(empty)]  tabs[tab0:circle  tab1:square  tab2:home] sel=0
STATE[第一次 wishlist] modal=[login]   (拦截 → 排队弹登录)
STATE[第二次 wishlist] modal=[login]   (登录卡复用,不叠层)
STATE[登录后]          modal=[wishlist](登录卡消失,Wishlist 呈现)
STATE[second modal]    modal=[wishlist | secondmodal]

6.3 HAP 验证

CompileArkTS: finished
PackageHap: finished
BUILD SUCCESSFUL
install bundle successfully
start ability successfully

6.4 功能用例

  1. Welcome「进入主界面」→ 三 Tab 容器替换根成功;
  2. 各 Tab 内 Push Product,params.id 正确注入显示;
  3. 「Push Color(替换栈顶)」后栈深度不变;
  4. 未登录打开 Wishlist → 弹登录卡,toast 提示被拦截;
  5. 登录卡开着重复触发 Wishlist → 登录卡不叠层;
  6. 登录 → 登录卡消失、Wishlist 出现、顶栏变「已登录」;
  7. Wishlist 内打开 Second Modal → 模态叠模态;
  8. 「添加 Star Tab」→ 动态出现第四个 Tab 并选中;
  9. 「直达 Circle」→ ClassFinder 命中已有屏,仅切 Tab 不重复入栈;
  10. 连点两个导航按钮 → 第二个被 SingleNavigationRouter 排队,日志抽屉可逐条查看。

6.5 验证结论

HAR 库模块、HAP 工程、签名链路和九个路由场景已完成验证。应用在真机上成功安装并进入前台,登录拦截续航、防重入排队、容器替换根等核心语义与上游一致;适配提交 鸿蒙适配 已推送至 AtomGit,远端 Git Hooks 检查通过。

七、运行效果

7.1 真机截图

在这里插入图片描述

7.2 命令速查

git clone https://atomgit.com/oh-tpc/route-composer.git
cd route-composer/Example/harmonyApp
ohpm install
hvigorw assembleHap -p product=default -p buildMode=debug --no-daemon   # 命令行验证
# 真机:DevEco Studio 打开 Example/harmonyApp → 自动签名 → Run
hdc -t <设备序列号> install -r entry/build/default/outputs/default/entry-default-signed.hap
hdc -t <设备序列号> shell aa start -a EntryAbility -b org.terminator.ohos.routecomposer -m entry

八、遗留问题与改进方向

8.1 踩坑复盘

  1. 容器装配缺锚点:CompleteFactoryAssembly 最初没有 .from(...),路由器找不到起点,「进入主界面」真机必败(Initial screen not found)。修复:装配器补 from(previousStep),Demo 锚定 GeneralStep.root();
  2. 防重入语义错了:SingleNavigationRouter 初版「导航中直接拒绝」,导致拦截器内部的 navigate(loginScreen) 被静默吞掉。上游语义是 postpone——改为排队后登录卡才能可靠弹出;
  3. 登录卡叠层:loginScreen 最初用 NilFinder(每次新建),连续触发会叠出多张登录卡,关一张还剩一张。改 ClassFinder 复用后不再叠层。以上三处全部由 Node 复现环境定位;
  4. macOS 大小写不敏感:上游已有 Example/, lowercase example/ 会并进同一目录,鸿蒙 Demo 只能放 Example/harmonyApp;
  5. ArkTS 语法硬约束:Swift 参数标签(perform(on:with:))非法、adding 重载必须拆名、类型别名桶导出要 export type、NavPathStack 是全局环境声明(不能从 @kit.ArkUI import)、pop 重载参数个数要与签名严格对齐;
  6. HAR 工程细节:HAR 模块需要自己的 src/main/module.json5;oh-package.json5 版本必须是严格 SemVer(2.22.2-ohos.1 会被警告拒绝);
  7. 双工具链缓存互踩:command-line-tools 与 DevEco Studio 的 hvigor 版本不同,混用后报 WithParamReplacement is not a function——切换前删 .hvigor/、oh_modules/ 与 lock 文件即可恢复;
  8. 签名材料不进仓:signingConfigs 保持空数组,.gitignore 覆盖 *.p12/*.cer/*.p7b/*.hap。

8.2 已知问题

  • ArkUI Navigation 无原生模态呈现,模态层是应用级覆盖卡片(无自定义转场 delegate、无 UISheetPresentation 等价物);
  • SearchOptions 六个选项做了并集简化(上游的 contained/presenting/parent 以「宿主内整栈 + 模态层」近似),逐项映射表见仓库中文主文档第 13 节;
  • UISplitViewController / Storyboard / Segue / SwiftUI 互操作无对应物,裁剪;
  • expectingContainer 等编译期泛型检查不存在,以运行时约定替代;
  • GlobalInterceptorRouter 的全局 ContextTask 无命中屏可施加,当前仅保留字段并打日志说明。

8.3 未来优化方向

  • 把 HAR 发布到 ohpm 中心仓,供业务工程直接依赖;
  • 补充 DetailsNavigationFinder、ClassWithContextFinder 更多场景与深链(Want/URL)入口示例;
  • 模态层接入 ArkUI 自定义转场动画,对齐上游 presentModally(transitioningDelegate:);
  • 为路由引擎补 ohosTest/hypium 单元测试,替代当前 Node 复现脚本;
  • 在 CI 中加入 HAR 编译、HAP 构建与 codelint 检查。

九、总结

9.1 核心难点回顾

RouteComposer (Swift, 151 文件)
    → 六大实体协议 + Any*/Box 类型擦除(ArkTS 接口 + 包装类)
    → DefaultRouter 三阶段引擎(逐语义移植)
    → 逻辑屏幕树(UIKit 容器 → RootHost/TabsHost/NavHost/ModalHost)
    → 九个 Action 平台落地(NavPathStack / Tabs / 覆盖层)
    → ArkUI 单页宿主(@Builder 屏幕目录 + 日志抽屉)
    → HAP 构建、签名、真机验收

9.2 封装层次

  • 库核心层:RCScreen/ScreenRef 值类型、六实体接口、Box 擦除、步骤链与装配 DSL;
  • 引擎层:DefaultRouter 三阶段导航(搜索起点 → 拦截器 → 递归构建),全程可日志;
  • 平台层:注册屏幕树四类宿主 + 镜像同步 + 九个 Action;
  • 宿主层:单页渲染器、屏幕目录 @Builder、登录/日志会话;
  • 交付层:HAR 模块 + HAP 工程、签名、安装与真机验收。

9.3 三条经验

  1. Swift 源库的鸿蒙适配没有跨语言捷径,先把「纯逻辑 / 平台绑定」切开,逻辑层逐语义移植、平台层显式建模,比硬做桥接稳得多;
  2. 声明式 UI 下做路由库,需要一个显式的屏幕树作为唯一事实源——树是模型、页面是渲染器,Finder/Action 的语义就能原样保留;
  3. 纯逻辑库值得配一个 Node 复现环境(桩掉 ArkUI 依赖),真机问题先在桌面上复现再修,登录流程三处缺陷就是这样定位的。

9.4 适配成果

当前适配已完成:

  • 上游 2.22.2 的 ArkTS 源码级移植(HAR 库模块,八个子模块、约 60 个公开导出);
  • 九个路由场景的 DevEco Stage Demo(含登录拦截续航、防重入排队、动态加 Tab);
  • README 四件套(含 6700+ 汉字中文主文档:映射总表、踩坑表、故障排查、已知限制);
  • Node 复现环境的逻辑回归与真机功能用例验证;
  • HAP 构建、签名、设备安装和真机运行效果图;
  • AtomGit 项目文档、文章与源码链接(https://atomgit.com/oh-tpc/route-composer)。

适配过程 FAQ

以下问题来自 ohosArm64 目标接入、NAPI 桥接、HAP 构建与真机验收的实际流程,按出现频率排序。

Q1:DevEco Studio Sync 失败或 ohpm 依赖下载超时,怎么处理?

  • 确认 DevEco Studio 版本与工程 build-profile.json5 的 compatibleSdkVersion 一致;
  • 在 File > Settings > Build, Execution, Deployment > Ohpm 配置可用的 ohpm 仓库镜像或代理;
  • 删除 oh_modules、oh-package-lock.json5 与 .hvigor 缓存后重新 Sync;
  • 公司网络需要代理时,同时给 ohpm 与 hvigor 配置 http.proxy。

Q2:CMake 报 Missing .../libxxx.so 或找不到 Kotlin/Native 动态库?

  • 先在 example 目录执行 ./gradlew :nativeApp:linkDebugSharedOhosArm64(模拟器用 linkDebugSharedOhosX64);
  • 确认 libxxx.so 与 libc++_shared.so 已复制到 ohos/entry/libs/<abi>/;
  • 检查 entry/build-profile.json5 的 abiFilters 与设备 ABI 是否一致(真机 arm64-v8a,模拟器 x86_64);
  • 清理 entry/.cxx 与 entry/build 后重试。

Q3:ArkTS 报 Cannot find module 'libxxx_napi.so' 或调用返回 undefined?

  • 确认 CMake 的 add_library(<target> SHARED ...) 名称与 import ... from 'libxxx_napi.so' 完全一致;
  • 解包 HAP,确认 libs/<abi>/libxxx_napi.so 已打包;
  • 删除 entry/build、entry/.cxx 与 oh_modules 后重新构建;
  • 检查 module.json5 的 srcEntry 与页面路径是否正确。

Q4:真机安装报 signature verification failed 或 install failed?

  • 在 DevEco Studio 的 Signing Configs 配置证书、Profile 与密码;
  • bundleName 必须与签名 profile 中的 bundle name 一致;
  • 设备需加入 profile 的 deviceIds,调试证书有效期需覆盖当前时间;
  • 用 hdc list targets 确认设备在线,再执行 hdc install -r xxx-signed.hap。

Q5:应用启动白屏、闪退或 NAPI 调用崩溃?

  • 用 hdc hilog | grep -i "napi\|kotlin" 查看原生日志;
  • 确认 libc++_shared.so 与 Kotlin/Native 工具链版本匹配,且 ABI 与设备一致;
  • 检查 C ABI 返回的字符串是否由 Native 侧分配、ArkTS 侧复制后是否调用 XxxFree 释放;
  • 在 DevEco 的 Native 调试配置中附加 lldb,定位空指针与越界访问。

Q6:Gradle 报 ohosArm64 目标不存在或 Kotlin/Native 编译失败?

  • 使用鸿蒙发行版 Kotlin(如 2.2.21-1.0.0),并在 settings.gradle.kts 配置 eazytec 仓库;
  • Gradle 建议 8.x、JDK 21,版本不匹配时先统一工具链;
  • 确认 kotlin { ohosArm64() } 已应用,且 local.properties 的 SDK 路径有效;
  • 清理 ~/.gradle/caches 中对应版本的 Kotlin/Native 缓存后重试。

Q7:HAP 构建报 SDK 版本不匹配或 compatibleSdkVersion 错误?

  • 对齐 DevEco Studio、HarmonyOS SDK 与 build-profile.json5 中的 compatibleSdkVersion;
  • 升级 DevEco Studio 后同步升级 hvigor 与 SDK,避免 modelVersion 与 IDE 不匹配;
  • 命令行构建时设置 DEVECO_SDK_HOME 指向 DevEco 的 sdk 目录。

Q8:命令行 hvigor 构建失败,但 DevEco 里能构建?

  • 使用 DevEco 自带的 node 与 hvigor:
    "$DEVECO/Contents/tools/node/bin/node" "$DEVECO/Contents/tools/hvigor/bin/hvigorw.js" ...;
  • 设置 JAVA_HOME、NODE_HOME、DEVECO_SDK_HOME;
  • 加上 --no-daemon 排除守护进程缓存影响,并清理 entry/build 与 .hvigor。

参考文档

Logo

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

更多推荐