文章

toona ohos introduction

toona ohos introduction

一款面向 HarmonyOS 的原生 Matrix 客户端,采用深模块架构,严格依赖注入、平台抽象和显式接缝边界设计。参考 ElementX/Element Android 客户端架构。

功能特性

类别功能
身份认证Homeserver 登录/注册,加密令牌持久化,自动会话恢复,令牌校验
同步长轮询同步引擎,增量同步(since token),房间状态协调
房间创建/加入/离开房间,房间别名,公共房间目录,房间设置
消息文本/表情/通知消息,编辑/删除/回复,反应,贴纸,已读回执
空间层级式房间组织,空间树导航,空间创建
端到端加密Olm/Megolm 加密,设备密钥管理,密钥验证(SAS + QR),设备信任级别
在线状态用户在线状态,状态消息
媒体图片上传/下载,缩略图生成,URL 预览
界面深色/浅色主题,房间标签过滤(收藏、未读、私聊),通知角标

架构设计

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
toona/src/main/ets/
├── network/
│   ├── ConnectionConfig.ets       # 令牌 + 服务器接缝(拉取模型)
│   └── clients/
│       ├── BaseHttpClient.ets     # 通用 HTTP(request, requestBinary, requestMediaJson, upload)
│       ├── RoomHttpClient.ets     # 房间 + 输入状态 + 回执 API
│       ├── MessageHttpClient.ets  # 消息发送 + 关系 API
│       ├── UserHttpClient.ets     # 用户资料 API
│       ├── DeviceHttpClient.ets   # 设备密钥 API
│       ├── MediaHttpClient.ets    # 媒体上传/下载/缩略图
│       ├── PresenceHttpClient.ets # 在线状态 API
│       └── OpenIdHttpClient.ets   # OpenID 令牌 API
├── platform/
│   └── adapters/
│       ├── IFilePicker.ets        # 文件选择接口
│       ├── IAudioRecorder.ets     # 音频录制接口
│       ├── IClipboard.ets         # 剪贴板接口
│       ├── HarmonyFilePicker.ets  # PhotoPicker + 文件 I/O 实现
│       ├── HarmonyAudioRecorder.ets # 麦克风 + AV 录音实现
│       └── HarmonyClipboard.ets   # 剪贴板实现
├── models/
│   ├── DataModels.ets             # Session, Room, Message, User
│   └── types/                     # MatrixTypes, SyncTypes, RoomTypes, MessageTypes, CryptoTypes
├── database/
│   ├── LocalDatabase.ets          # SQLite 门面(Room ORM)
│   ├── ToonaDatabase.ets          # Room 数据库定义
│   ├── dao/                       # CryptoDao, DeviceKeysDao, MessageDao, RoomDao, UserDao
│   └── entities/                  # ORM 实体类
├── services/
│   ├── ServiceRegistry.ets        # AppStorage 支撑的服务定位器(init/getCurrent)
│   ├── AuthManager.ets            # IAuthManager 实现 — 登录、会话、令牌生命周期
│   ├── SyncManager.ets            # ISyncManager 实现 — 长轮询同步循环
│   ├── RoomManager.ets            # IRoomManager 实现 — 房间 CRUD
│   ├── MessageManager.ets         # 消息发送/编辑/删除
│   ├── CryptoService.ets          # 端到端加密:设备密钥、Megolm 会话、解密
│   ├── VerificationService.ets    # SAS + QR 密钥验证
│   ├── CryptoManager.ets          # 设备密钥上传、一次性密钥管理
│   ├── MediaManager.ets           # 媒体上传/下载/HTTP URL 生成
│   ├── DeviceManager.ets          # 设备列表管理
│   ├── FilterManager.ets          # 同步过滤器 CRUD
│   ├── PresenceManager.ets        # 在线状态管理
│   ├── PushManager.ets            # 推送规则管理
│   ├── RoomStateManager.ets       # 房间状态查询
│   ├── RoomTagManager.ets         # 房间标签(收藏)管理
│   ├── ThirdpartyManager.ets      # 第三方协议搜索
│   ├── UserManager.ets            # 用户资料管理
│   ├── VoIPManager.ets            # TURN 服务器凭证
│   ├── OpenIdManager.ets          # OpenID 令牌交换
│   ├── EventProcessor.ets         # Matrix 事件分发
│   ├── MessageActionHandler.ets   # 消息动作(发送/编辑/删除/置顶/反应/举报/分享/复制)
│   ├── MediaActionHandler.ets     # 媒体动作(选择图片/录音/上传)
│   └── RoomMetadataLoader.ets     # 房间主题 + 成员在线状态 + 输入状态
├── pages/
│   ├── AppNavigation.ets          # 根组合根(创建所有服务)
│   ├── LoginPage.ets              # 登录/注册
│   ├── MainPage.ets               # 带标签页的 Shell(房间/空间)
│   ├── RoomListPage.ets           # 带过滤的房间列表
│   ├── SpacePage.ets              # 空间树导航
│   ├── RoomDetailPage.ets         # 聊天视图(handlers + adapters 的组合根)
│   ├── SettingsPage.ets           # 用户设置 + 设备管理
│   └── ...                        # SearchPage, JoinRoomPage, JoinSpacePage 等
├── pages/components/
│   ├── MessageBubble.ets          # 消息显示(接收 mediaManager + verificationService)
│   ├── ImageViewer.ets            # 全屏图片(接收 mediaManager)
│   ├── FilePreview.ets            # 文件附件预览
│   └── verification/              # QrCodeVerificationDialog, SasVerificationDialog, VerificationRequestDialog
├── crypto/
│   ├── OlmWrapper.ets             # libolm 绑定
│   └── types/CryptoTypes.ets      # 加密类型定义
├── utils/
│   ├── AppLogger.ets              # 结构化日志
│   ├── ThemeManager.ets           # 主题 + 颜色系统
│   ├── MxcUtils.ets               # mxc:// URL 处理
│   ├── MessageFormatter.ets       # 纯格式化函数(时间、回复发送者)
│   ├── RoomStateParser.ets        # 房间状态事件 → Room 模型
│   └── ToastUtils.ets             # Toast 通知
└── toonaability/                  # ToonaAbility 入口

设计原则

深模块,浅框架。 每个服务拥有精简的公开接口(IAuthManagerIRoomManagerISyncManager),将实现细节隐藏其后。横切关注点(文件选择、音频录制、剪贴板)通过平台适配器接口隔离。

构造器注入,无隐藏单例。 所有服务通过构造器参数接收依赖。组合根(AppNavigation.initializeServices())负责装配整个依赖图。服务内部不调用 getInstance()——仅 Room ORM DAO 在初始化时使用。

平台接缝。 IFilePickerIAudioRecorderIClipboard 将 HarmonyOS SDK API 隔离在接口之后。业务逻辑依赖接口,页面构造 Harmony 实现。

服务注册表作为有界定位器。 NavDestination 页面(由 Navigation 框架实例化)通过 ServiceRegistry.getCurrent() 访问服务。这是定位器的唯一合法用途——内联子 @Component 页面通过父组件传递的属性接收依赖。

技术栈

技术
框架HarmonyOS + ArkTS 严格模式
架构MVVM + 仓库模式 + 构造器注入
导航NavPathStack / NavDestination(非 @ohos.router
存储SQLite(@mlethe/room ORM)+ SPHelper 加密偏好
网络@kit.NetworkKit HTTP 客户端(通过 BaseHttpClient
加密libolm(Olm + Megolm)(通过 OlmWrapper

依赖图

1
2
3
4
5
6
7
AppNavigation(组合根)
 ├── ConnectionConfig ←── AuthManager, 所有 HttpClients
 ├── AuthManager (IAuthManager) ←── SyncManager, RoomManager, MessageManager, ...
 ├── CryptoDao + DeviceKeysStorage ←── CryptoService, VerificationService
 ├── CryptoService ←── EventProcessor ←── SyncManager
 ├── 平台适配器 ←── MediaActionHandler, MessageActionHandler ←── RoomDetailPage
 └── ServiceRegistry(AppStorage 支撑)←── NavDestination 页面

快速开始

环境要求

  • DevEco Studio 5.0+
  • HarmonyOS SDK(API 12+)
  • Node.js 18+(用于 hvigor 构建系统)

编译

1
2
3
4
5
6
7
8
9
10
11
# 调试编译
hvigor build -m toona

# 完整项目编译
hvigor build

# 清理编译产物
hvigor clean

# 发布编译
hvigor assembleRelease

运行

  1. 在 DevEco Studio 中打开项目
  2. 连接 HarmonyOS 设备或启动模拟器
  3. 选择 toona 模块 → 运行

导航架构

应用使用 NavPathStack / NavDestination(非 @ohos.router)。

页面类型导航方式
AppNavigation@EntryNavigation(pathStack) — 加载 LoginPage 或 MainPage
LoginPageNavDestination首屏;登录后替换为 MainPage
MainPageNavDestination带标签页 Shell(RoomListPage + SpacePage)
RoomDetailPageNavDestination聊天视图,通过 pushPathByName('RoomDetailPage', { roomId, roomName }) 推入
SettingsPageNavDestination设置页,通过 pushPathByName('SettingsPage', ...) 推入
RoomListPage@Component 子组件由 MainPage 内嵌,通过 props 接收 ISyncManagerIRoomManagerRoomTagManager
SpacePage@Component 子组件由 MainPage 内嵌,通过 props 接收 IRoomManager

子页面导航方式:

1
2
3
4
this.pathStack.pushPathByName('RoomDetailPage', {
  "roomId": room.id,
  "roomName": room.getDisplayName()
} as Record<string, string>);

新增功能

1. 添加 API 端点

在对应的 *HttpClient.ets 中添加方法:

1
2
3
4
5
// 在 RoomHttpClient.ets 中:
async getRoomTags(userId: string): Promise<RoomTagsResponse> {
  const encodedUserId = encodeURIComponent(userId);
  return await this.get<RoomTagsResponse>(`/user/${encodedUserId}/account_data/m.tag`);
}

如果 API 非常浅(仅 1 个方法,只是 get/post 的包装),添加到已有客户端——不要创建新客户端。

2. 添加响应类型

models/types/MatrixTypes.ets 中定义响应类型。

3. 添加服务方法

在对应的 Manager 中添加方法;如果是新领域,创建新 Manager。

4. 添加页面

  1. pages/ 中创建 .ets 文件
  2. 添加 @Builder 导出函数(router_map.json 要求):
1
2
3
4
@Builder
export function MyPageBuilder(name: string, param: Object) {
  MyPage()
}
  1. resources/base/profile/router_map.jsonmain_pages.json 中注册
  2. 如果是内联子 @Component,通过 props 接收依赖(非 ServiceRegistry)

5. 在组合根中装配

新增服务:在 AppNavigation.initializeServices() 中创建实例,注册到 ServiceRegistry

测试

  • 单元测试toona/src/test/**/*.test.ets — 通过 DevEco Studio 测试运行器或 hvigor test 执行
  • 设备测试toona/src/ohosTest/ets/test/*.test.ets
  • 测试框架@ohos/hypium

代码规范

本项目强制执行 ArkTS 严格模式arkts-* 规则标签):

规则说明
arkts-no-standalone-this禁止在类方法外使用 this
arkts-no-untyped-obj-literals对象字面量必须有显式类型上下文
arkts-no-any-unknown禁止 anyunknown 类型
arkts-no-destructuring禁止解构声明
arkts-no-as-const禁止 as const 断言

额外约定:

  • 所有变量和参数必须有显式类型注解
  • throw new Error(...) — 禁止抛出字符串
  • 使用 NavPathStack / NavDestination — 禁止 @ohos.router
  • 构造器注入 — 服务类内部禁止 getInstance()
  • 使用 @kit.NetworkKit HTTP — 禁止 fetch() 一款面向 HarmonyOS 的原生 Matrix 客户端,采用深模块架构,严格依赖注入、平台抽象和显式接缝边界设计。参考 ElementX/Element Android 客户端架构。

功能特性

类别功能
身份认证Homeserver 登录/注册,加密令牌持久化,自动会话恢复,令牌校验
同步长轮询同步引擎,增量同步(since token),房间状态协调
房间创建/加入/离开房间,房间别名,公共房间目录,房间设置
消息文本/表情/通知消息,编辑/删除/回复,反应,贴纸,已读回执
空间层级式房间组织,空间树导航,空间创建
端到端加密Olm/Megolm 加密,设备密钥管理,密钥验证(SAS + QR),设备信任级别
在线状态用户在线状态,状态消息
媒体图片上传/下载,缩略图生成,URL 预览
界面深色/浅色主题,房间标签过滤(收藏、未读、私聊),通知角标

设计原则

深模块,浅框架。 每个服务拥有精简的公开接口(IAuthManagerIRoomManagerISyncManager),将实现细节隐藏其后。横切关注点(文件选择、音频录制、剪贴板)通过平台适配器接口隔离。

构造器注入,无隐藏单例。 所有服务通过构造器参数接收依赖。组合根(AppNavigation.initializeServices())负责装配整个依赖图。服务内部不调用 getInstance()——仅 Room ORM DAO 在初始化时使用。

平台接缝。 IFilePickerIAudioRecorderIClipboard 将 HarmonyOS SDK API 隔离在接口之后。业务逻辑依赖接口,页面构造 Harmony 实现。

服务注册表作为有界定位器。 NavDestination 页面(由 Navigation 框架实例化)通过 ServiceRegistry.getCurrent() 访问服务。这是定位器的唯一合法用途——内联子 @Component 页面通过父组件传递的属性接收依赖。

技术栈

技术
框架HarmonyOS + ArkTS 严格模式
架构MVVM + 仓库模式 + 构造器注入
导航NavPathStack / NavDestination(非 @ohos.router
存储SQLite(@mlethe/room ORM)+ SPHelper 加密偏好
网络@kit.NetworkKit HTTP 客户端(通过 BaseHttpClient
加密libolm(Olm + Megolm)(通过 OlmWrapper

依赖图

1
2
3
4
5
6
7
AppNavigation(组合根)
 ├── ConnectionConfig ←── AuthManager, 所有 HttpClients
 ├── AuthManager (IAuthManager) ←── SyncManager, RoomManager, MessageManager, ...
 ├── CryptoDao + DeviceKeysStorage ←── CryptoService, VerificationService
 ├── CryptoService ←── EventProcessor ←── SyncManager
 ├── 平台适配器 ←── MediaActionHandler, MessageActionHandler ←── RoomDetailPage
 └── ServiceRegistry(AppStorage 支撑)←── NavDestination 页面

快速开始

环境要求

  • DevEco Studio 5.0+
  • HarmonyOS SDK(API 12+)
  • Node.js 18+(用于 hvigor 构建系统)

编译

1
2
3
4
5
6
7
8
9
10
11
# 调试编译
hvigor build -m toona

# 完整项目编译
hvigor build

# 清理编译产物
hvigor clean

# 发布编译
hvigor assembleRelease

运行

  1. 在 DevEco Studio 中打开项目
  2. 连接 HarmonyOS 设备或启动模拟器
  3. 选择 toona 模块 → 运行

导航架构

应用使用 NavPathStack / NavDestination(非 @ohos.router)。

页面类型导航方式
AppNavigation@EntryNavigation(pathStack) — 加载 LoginPage 或 MainPage
LoginPageNavDestination首屏;登录后替换为 MainPage
MainPageNavDestination带标签页 Shell(RoomListPage + SpacePage)
RoomDetailPageNavDestination聊天视图,通过 pushPathByName('RoomDetailPage', { roomId, roomName }) 推入
SettingsPageNavDestination设置页,通过 pushPathByName('SettingsPage', ...) 推入
RoomListPage@Component 子组件由 MainPage 内嵌,通过 props 接收 ISyncManagerIRoomManagerRoomTagManager
SpacePage@Component 子组件由 MainPage 内嵌,通过 props 接收 IRoomManager

子页面导航方式:

1
2
3
4
this.pathStack.pushPathByName('RoomDetailPage', {
  "roomId": room.id,
  "roomName": room.getDisplayName()
} as Record<string, string>);

新增功能

1. 添加 API 端点

在对应的 *HttpClient.ets 中添加方法:

1
2
3
4
5
// 在 RoomHttpClient.ets 中:
async getRoomTags(userId: string): Promise<RoomTagsResponse> {
  const encodedUserId = encodeURIComponent(userId);
  return await this.get<RoomTagsResponse>(`/user/${encodedUserId}/account_data/m.tag`);
}

如果 API 非常浅(仅 1 个方法,只是 get/post 的包装),添加到已有客户端——不要创建新客户端。

2. 添加响应类型

models/types/MatrixTypes.ets 中定义响应类型。

3. 添加服务方法

在对应的 Manager 中添加方法;如果是新领域,创建新 Manager。

4. 添加页面

  1. pages/ 中创建 .ets 文件
  2. 添加 @Builder 导出函数(router_map.json 要求):
1
2
3
4
@Builder
export function MyPageBuilder(name: string, param: Object) {
  MyPage()
}
  1. resources/base/profile/router_map.jsonmain_pages.json 中注册
  2. 如果是内联子 @Component,通过 props 接收依赖(非 ServiceRegistry)

5. 在组合根中装配

新增服务:在 AppNavigation.initializeServices() 中创建实例,注册到 ServiceRegistry

测试

  • 单元测试toona/src/test/**/*.test.ets — 通过 DevEco Studio 测试运行器或 hvigor test 执行
  • 设备测试toona/src/ohosTest/ets/test/*.test.ets
  • 测试框架@ohos/hypium

代码规范

本项目强制执行 ArkTS 严格模式arkts-* 规则标签):

规则说明
arkts-no-standalone-this禁止在类方法外使用 this
arkts-no-untyped-obj-literals对象字面量必须有显式类型上下文
arkts-no-any-unknown禁止 anyunknown 类型
arkts-no-destructuring禁止解构声明
arkts-no-as-const禁止 as const 断言

额外约定:

  • 所有变量和参数必须有显式类型注解
  • throw new Error(...) — 禁止抛出字符串
  • 使用 NavPathStack / NavDestination — 禁止 @ohos.router
  • 构造器注入 — 服务类内部禁止 getInstance()
  • 使用 @kit.NetworkKit HTTP — 禁止 fetch()
本文由作者按照 CC BY 4.0 进行授权