鸿蒙原生ArkTS-宇宙知识AI问答


一、项目背景
1.1 为什么选择鸿蒙原生开发
随着华为鸿蒙操作系统(HarmonyOS)的快速发展,越来越多的开发者开始关注鸿蒙原生应用开发。HarmonyOS 作为面向全场景的分布式操作系统,不仅覆盖手机、平板、智能穿戴等设备,还提供了独特的 ArkTS 语言作为主要开发语言。ArkTS 基于 TypeScript 进行了扩展,保留了 TypeScript 的类型安全性和语法简洁性,同时融入了声明式 UI 编程范式,使得 UI 开发更加直观和高效。
本项目的诞生源于两个需求的交汇:一方面是对宇宙知识科普的需求日益增长,人们渴望用通俗易懂的方式了解天文学、宇宙学的奥秘;另一方面是探索鸿蒙原生应用开发的最佳实践,尤其是 ArkTS 语言在实际项目中的应用效果。于是,「宇宙知识AI问答」应运而生——一个基于 HarmonyOS 原生 ArkTS 开发的智能问答应用,集成了 AI 大模型能力,能够用专业且通俗的语言回答各种宇宙知识问题。
1.2 项目目标
本项目旨在打造一个面向普通用户的宇宙知识科普问答平台,具有以下核心目标:
- 原生体验:充分利用 HarmonyOS 原生 API,提供流畅、稳定的用户体验。
- 智能问答:集成大语言模型(DeepSeek-V3),实现高质量的宇宙知识问答。
- 流式输出:采用 SSE(Server-Sent Events)流式传输技术,实现 AI 回复的逐字呈现,提升交互感。
- 友好界面:采用 ArkTS 声明式 UI 架构,构建美观、直观的聊天界面。
- 代码规范:遵循 ArkTS 最佳实践,展示模块化、可维护的代码结构。
1.3 技术选型
| 技术组件 | 选择 | 说明 |
|---|---|---|
| 开发语言 | ArkTS | 鸿蒙原生声明式 UI 语言 |
| 开发框架 | HarmonyOS NEXT API | 最新原生开发框架 |
| AI 模型 | DeepSeek-V3 | 高性能大语言模型 |
| 网络请求 | @kit.NetworkKit | 鸿蒙原生 HTTP 网络库 |
| 数据传输 | SSE 流式传输 | 实时增量更新 |
| UI 构建 | @Builder 装饰器 | 组件化 UI 构建方案 |
二、项目结构全览
2.1 目录层次
本项目的目录结构遵循 HarmonyOS 标准工程规范,主要代码集中在 entry/src/main/ets/pages/ 目录下:
Demo0528/
├── entry/
│ └── src/
│ └── main/
│ └── ets/
│ └── pages/
│ ├── Index.ets # 主页面(聊天界面)
│ ├── AIChatService.ets # AI 服务层(网络请求+SSE解析)
│ └── ColumnStartLayout.ets # 其他布局组件
├── build-profile.json5 # 项目构建配置
├── hvigor/ # 构建工具配置
├── AppScope/ # 应用级配置
└── oh-package.json5 # 包依赖管理
2.2 模块职责划分
项目采用了明确的职责分离架构,共分为三个核心层次:
UI 表现层(Index.ets):负责所有用户界面元素的构建和交互逻辑,包括聊天消息展示、输入框、按钮、加载动画等。通过 @Builder 装饰器将 UI 拆分为多个独立构建函数,每个函数负责一个独立的 UI 区域。
AI 服务层(AIChatService.ets):封装了所有与 AI API 通信的逻辑,包括 HTTP 请求的发起、SSE 流式数据的解析、非流式回退机制以及请求取消功能。该层对上层完全黑盒,仅通过定义清晰的接口(AICallbacks、ChatMessage)与 UI 层交互。
配置管理层(build-profile.json5、module.json5 等):管理网络权限、应用签名、模块配置等基础设施,确保应用能够正常访问网络接口。
这种分层设计使得代码具有良好的可测试性和可维护性。当需要更换 AI 模型提供商时,只需修改 AIChatService.ets 中的 API 配置和解析逻辑,UI 层完全无需改动。
三、ArkTS 声明式 UI 深入解析
3.1 声明式 UI 编程范式
ArkTS 采用的声明式 UI 编程范式,与传统的命令式 UI(如 Java Android 的 XML+Activity)有本质区别。在声明式 UI 中,开发者描述 UI “应该是什么样”,而非"如何一步步构建"。状态驱动视图更新——当 @State 装饰的变量发生变化时,框架自动重新渲染相关组件。
以本项目的聊天消息列表为例:
@State messageList: ChatMessage[] = [];
@State isLoading: boolean = false;
@State streamingContent: string = '';
@State inputText: string = '';
@State errorMsg: string = '';
build() {
Column() {
this.buildHeader(); // 标题栏
Column() {
this.buildChatArea() // 聊天区
}
.layoutWeight(1);
this.buildInputArea(); // 输入区
}
.width('100%')
.height('100%')
.backgroundColor('#E8F0FF')
}
当 messageList 新增一条消息、isLoading 变为 true 或 streamingContent 收到新的 token 时,框架会自动触发 UI 重新渲染,开发者无需手动操作 DOM。
3.2 @Builder 组件化构建
ArkTS 的 @Builder 装饰器是实现组件化 UI 的核心工具。它允许将 UI 片段的构建逻辑封装为独立函数,类似其他框架中的"组件"概念。本项目共使用了 7 个 @Builder 函数:
| @Builder 函数 | 职责 | 渲染条件 |
|---|---|---|
buildHeader() |
顶部标题栏(标题+副标题) | 始终显示 |
buildChatArea() |
聊天消息列表容器 | 始终显示 |
buildWelcomeMessage() |
欢迎界面(初始状态) | messageList.length === 0 |
buildUserBubble(msg) |
用户消息气泡 | 每条 role === ‘user’ 的消息 |
buildAIBubble(msg) |
AI 消息气泡 | 每条 role === ‘assistant’ 的消息 |
buildLoadingIndicator() |
AI 正在回复的加载动画 | isLoading && !streamingContent |
buildErrorBubble(err) |
错误提示气泡 | errorMsg 不为空 |
buildInputArea() |
底部输入区域 | 始终显示 |
这种拆分带来了以下好处:
- 关注点分离:每个 Builder 只负责一个 UI 区域的构建逻辑,代码清晰可读。
- 条件渲染:通过
if条件判断,灵活控制每个 Builder 的显示/隐藏。 - 状态驱动:Builder 内部直接引用
@State变量,状态变化时自动更新。 - 复用性:同一个 Builder(如
buildAIBubble)可以在多个地方调用,如展示完整回复和流式中间状态。
3.3 聊天消息区的渲染策略
聊天消息区是整个应用最复杂的 UI 区域,需要同时处理多种状态:
@Builder
buildChatArea() {
Scroll() {
Column() {
// 第一阶段:空状态 → 显示欢迎界面
if (this.messageList.length === 0) {
this.buildWelcomeMessage();
}
// 第二阶段:历史消息 → 逐条渲染气泡
ForEach(this.messageList, (msg: ChatMessage) => {
if (msg.role === 'user') {
this.buildUserBubble(msg.content);
} else {
this.buildAIBubble(msg.content);
}
}, (msg: ChatMessage) => msg.role + '#' + msg.content)
// 第三阶段:流式输出中 → 显示实时内容
if (this.isLoading && this.streamingContent) {
this.buildAIBubble(this.streamingContent + ' ▍');
}
// 第四阶段:等待中 → 显示加载动画
if (this.isLoading && !this.streamingContent) {
this.buildLoadingIndicator();
}
// 第五阶段:出错 → 显示错误提示
if (this.errorMsg) {
this.buildErrorBubble(this.errorMsg);
}
Blank().height(12)
}
}
}
这种渲染策略确保了所有 UI 状态都有对应的呈现方式,不会出现空白屏幕或状态残留。
四、界面设计与交互体验
4.1 视觉风格
应用采用深蓝星空主题色系,营造出宇宙探索的氛围感:
| UI 元素 | 颜色值 | 色系说明 |
|---|---|---|
| 页面背景 | #E8F0FF |
淡蓝星空 |
| 主色调 | #3B6CE8 |
深邃蓝 |
| 强调色 | #2B5CC8 |
夜空蓝 |
| 辅助色 | #7BA0F0 |
星辰蓝 |
| 边框色 | #C5D8F8 |
淡蓝辉光 |
| 按钮底色 | #EFF4FF |
极浅蓝 |
| 文字深色 | #3D2B1F |
深棕(阅读舒适) |
4.2 标题栏设计
标题栏采用纯白背景,中央展示应用名称和副标题:
┌─────────────────────┐
│ 🌌 宇宙知识AI问答 │ ← 22sp 加粗
│ 探索星辰大海,解答宇宙奥秘 │ ← 13sp 辅助文字
└─────────────────────┘
标题使用 Emoji 🌌 作为图标替代,避免了加载图片资源的开销,同时视觉效果鲜明。副标题的提示文字采用辅助色 #7BA0F0,与主标题形成层次感。
4.3 欢迎界面设计
当用户首次打开应用时,聊天区域显示精心设计的欢迎界面,包含引导文案和示例问题按钮:
🌍 ← 48sp 大号 Emoji
你对宇宙的哪方面感到好奇? ← 引导文案
输入你的问题,我来用科学、
通俗的方式为你解答 🌟 ← 副引导
试试这些问题 👇
┌────────────────────────┐
│ 宇宙的年龄有多大? │ ← 示例问题按钮1
└────────────────────────┘
┌────────────────────────┐
│ 黑洞里面是什么? │ ← 示例问题按钮2
└────────────────────────┘
┌────────────────────────┐
│ 人类能移民火星吗? │ ← 示例问题按钮3
└────────────────────────┘
🎲 换一批话题 ← 随机轮换按钮
欢迎界面的设计理念是"零门槛入门"——用户不需要学习如何使用,点击示例按钮或直接输入即可开始对话。这是良好用户体验的典型实践。
4.4 示例问题轮换机制
示例问题共 6 个,覆盖了宇宙知识的多个维度:
- “宇宙的年龄有多大?”——宇宙学基础
- “黑洞里面是什么?”——天体物理热门
- “人类能移民火星吗?需要多久?”——航天探索
- “暗物质和暗能量到底是什么?”——前沿理论
- “宇宙大爆炸之前发生了什么?”——哲学+科学
- “太阳系外有适合居住的星球吗?”——系外行星探索
"换一批话题"按钮通过 rotateSamples() 方法实现问题的随机排列:
rotateSamples(): void {
const arr: string[] = this.sampleQuestions;
for (let i: number = 0; i < arr.length; i++) {
const j: number = (i * 7 + 3) % arr.length;
const tmp: string = arr[i];
arr[i] = arr[j];
arr[j] = tmp;
}
this.sampleQuestions = [...arr];
}
这是一种简单的伪随机洗牌算法,避免了复杂数学运算,在 ArkTS 中高效执行。使用 [...arr] 展开运算符创建新数组,触发 ArkTS 的状态更新检测。
4.5 对话气泡设计
用户消息和 AI 回复采用不同的气泡样式,通过视觉差异帮助用户快速区分消息来源:
用户气泡:深蓝底色、白色文字、右侧对齐、右上角小圆角("尾巴"在右上)。
Text(content)
.backgroundColor('#3B6CE8')
.borderRadius({ topLeft: 18, topRight: 4, bottomLeft: 18, bottomRight: 18 })
AI 气泡:白色底色、深色文字、左侧对齐、左上角小圆角("尾巴"在左上)、蓝色边框。
Text(content)
.backgroundColor('#FFFFFF')
.border({ width: 1, color: '#C5D8F8' })
.borderRadius({ topLeft: 4, topRight: 18, bottomLeft: 18, bottomRight: 18 })
气泡的圆角设计模仿了即时通讯软件的常见样式,topRight: 4(用户)和 topLeft: 4(AI)营造出聊天气泡的"尾巴"效果,使对话界面更加自然。
4.6 底部输入区域
底部输入区固定在屏幕最下方,包含文本输入框和发送按钮:
┌──────────────────────────────────────┐
│ ┌────────────────────┐ ┌────────┐ │
│ │ 输入你想了解的... │ │ 发送 │ │
│ └────────────────────┘ └────────┘ │
│ (加载时显示:⏹ 取消回复) │
└──────────────────────────────────────┘
输入框采用圆角胶囊样式(borderRadius: 22),高度 44vp,适合单手操作。发送按钮在输入框为空或 AI 正在回复时自动禁用(enabled 属性),这是一种常见的防误触设计。
支持键盘回车提交(onSubmit 回调),点击和回车两种方式均可发送消息。
4.7 加载状态与交互反馈
当 AI 正在回复时,应用通过三种方式提供反馈:
- 加载动画:在 AI 尚未返回任何内容时,显示 “🚀 正在探索宇宙…” 的加载气泡。
- 流式输出:当 AI 开始返回数据时,实时显示正在生成的文字,末尾带
▍光标符号模拟打字效果。 - 取消按钮:底部输入区域出现 “⏹ 取消回复” 按钮,用户可以随时中断 AI 回复。
取消操作的处理逻辑也很细致:
handleCancel(): void {
cancelAI();
this.isLoading = false;
// 保留已收到的流式内容作为 AI 回复
if (this.streamingContent) {
const aiMsg: ChatMessage = { role: 'assistant', content: this.streamingContent };
this.messageList = [...this.messageList, aiMsg];
}
this.streamingContent = '';
}
即使取消,之前收到的部分 AI 回复也不会丢失,而是作为完整的 AI 消息保存到聊天历史中——这体现了良好的用户体验设计。
五、AI 服务层深度剖析
5.1 架构设计
AIChatService.ets 是应用的核心服务层,负责与 AI API 的通信。其设计遵循以下原则:
- 单一职责:只负责网络请求和数据解析,不涉及任何 UI 逻辑。
- 接口隔离:通过
AICallbacks接口定义与 UI 层的通信契约。 - 防御性编程:多种回退机制确保在各种网络环境下都能工作。
- 可取消性:支持运行时取消请求,避免资源浪费。
5.2 类型定义
export interface ChatMessage {
role: string;
content: string;
}
export interface ChatCompletionRequest {
model: string;
messages: ChatMessage[];
stream: boolean;
max_tokens: number;
temperature: number;
top_p: number;
frequency_penalty: number;
thinking_budget: number;
}
export interface AICallbacks {
onData: (text: string) => void;
onDone: () => void;
onError: (errMsg: string) => void;
}
ChatMessage 采用与 OpenAI API 兼容的消息格式,role 可以是 system、user 或 assistant。AICallbacks 定义了三个回调,覆盖了 AI 交互的全部生命周期:数据到达 → 完成 → 出错。
5.3 系统提示词设计
系统提示词(System Prompt)是决定 AI 回答质量的关键因素。本项目精心设计了提示词,确保 AI 的回答既专业又易懂:
const SYSTEM_PROMPT: string =
'你是一位资深的宇宙知识专家,擅长解答关于天文学、宇宙学、天体物理等领域的科学问题。\n\n' +
'以下是你要遵循的原则:\n' +
'1. 严谨准确:基于公认的科学理论和观测数据回答问题,不传播伪科学或未经证实的假说。\n' +
'2. 通俗易懂:用简洁明了的语言解释复杂概念,避免过度使用专业术语而不加说明。\n' +
'3. 深入浅出:在保证准确性的前提下,适当使用类比帮助非专业用户理解。\n' +
'4. 鼓励探索:激发用户对宇宙的好奇心,推荐进一步阅读的方向或相关话题。\n' +
'5. 区分确定与不确定:对于前沿理论或尚存争议的话题,明确说明当前科学界的共识程度。\n\n' +
'请用中文回复,保持专业且友好。';
五个原则分别对应准确度、可理解性、教学效果、用户粘性和诚实度——这些是科普类 AI 助手的核心品质。
5.4 DeepSeek-V3 模型配置
请求体配置了 DeepSeek-V3 模型的各项参数:
| 参数 | 值 | 作用 |
|---|---|---|
| model | deepseek-ai/DeepSeek-V3 | 指定模型 |
| stream | true | 启用流式输出 |
| max_tokens | 2048 | 最大回复长度 |
| temperature | 0.6 | 创造性/确定性平衡 |
| top_p | 0.95 | 采样范围 |
| frequency_penalty | 0 | 频率惩罚 |
| thinking_budget | 2048 | 思考预算 |
temperature: 0.6 是一个经过调优的值——相比默认的 0.7 稍微降低了一些随机性,使得科学类回答更加稳定可靠。
5.5 SSE 流式传输实现
SSE(Server-Sent Events)是一种基于 HTTP 的实时数据传输协议。AI 模型逐个 token 生成回复内容,通过 SSE 实时推送给客户端,实现"逐字输出"的效果。
数据接收流程:
HTTP POST → 建立连接 → data: {"choices":[{"delta":{"content":"宇宙"}}]}
→ data: {"choices":[{"delta":{"content":"的"}}]}
→ data: {"choices":[{"delta":{"content":"年龄"}}]}
→ data: {"choices":[{"delta":{"content":"约"}}]}
→ data: [DONE](结束标记)
→ 连接关闭
ArkTS 实现:
httpRequest.on('dataReceive', (data: ArrayBuffer) => {
const text = arrayBufferToString(data);
buffer += text;
const lines = buffer.split('\n');
buffer = lines.pop() ?? ''; // 不完整行保留到下次
for (const line of lines) {
const trimmed = line.trim();
if (!trimmed.startsWith('data:')) continue;
if (trimmed === 'data:[DONE]') {
isDone = true;
callbacks.onDone();
continue;
}
const content = parseSSEDataLine(trimmed);
if (content) {
callbacks.onData(content);
}
}
});
这里有一个关键细节:buffer = lines.pop() ?? ''。由于网络传输是分片进行的,最后一行可能不完整(只收到了半个 JSON),所以需要将其保存到下次 dataReceive 事件再处理。这是 SSE 解析的标准实践。
5.6 双模式回退机制
HarmonyOS 不同版本和不同设备上的 http 模块行为存在差异。某些版本在 SSE 场景下不会触发 dataReceive 事件。为了解决这个兼容性问题,本项目实现了双模式回退机制:
模式一(流式):dataReceive 事件正常触发,逐个 token 解析并回调 onData。
模式二(非流式回退):如果 dataReceive 从未被触发(receivedAnyData 仍为 false),从 request 回调的完整响应体中解析结果:
if (!receivedAnyData && resp.result) {
// 先尝试按 SSE 格式解析(多个 data: 行)
const sseContent = parseFullSSEBody(bodyStr);
if (sseContent) {
callbacks.onData(sseContent);
} else {
// 再尝试非流式 JSON 格式
const jsonContent = parseNonStreamingBody(bodyStr);
if (jsonContent) {
callbacks.onData(jsonContent);
} else {
// 解析失败,返回错误提示
callbacks.onError(`无法解析响应: ${preview}`);
}
}
callbacks.onDone();
}
这种设计确保了应用在不同 HarmonyOS 版本上都能正常工作——不支持 SSE 的设备也能获得完整的 AI 回复,只是缺少逐字输出的视觉效果。
5.7 SSE 数据行解析
parseSSEDataLine 函数负责从单行 SSE 数据中提取 content:
function parseSSEDataLine(line: string): string | null {
const jsonStr = line.slice(5).trim();
if (!jsonStr) return null;
try {
const parsed = JSON.parse(jsonStr);
const choices = parsed.choices;
if (choices && choices.length > 0) {
const choice = choices[0];
// 兼容 delta 和 message 两种格式
const delta = choice.delta;
if (delta) return delta.content;
const message = choice.message;
if (message) return message.content;
}
} catch (_) { /* JSON 解析失败,跳过 */ }
return null;
}
注意 line.slice(5) 的作用是去掉 SSE 行开头的 “data:” 前缀(5 个字符)。同时兼容了 delta(流式格式)和 message(非流式格式)两种结构,增加了鲁棒性。
5.8 取消请求机制
为了支持用户随时中断 AI 回复,代码维护了一个全局的 HTTP 请求引用:
let httpRequestTask: http.HttpRequest | null = null;
export function queryAI(callbacks, messages) {
// 先取消上一次请求
if (httpRequestTask) {
httpRequestTask.destroy();
httpRequestTask = null;
}
const httpRequest = http.createHttp();
httpRequestTask = httpRequest;
// ... 发起请求 ...
}
export function cancelAI(): void {
if (httpRequestTask) {
httpRequestTask.destroy();
httpRequestTask = null;
}
}
每次发起新请求前先销毁旧请求,防止多次请求的资源泄露。cancelAI() 由 UI 层的取消按钮触发。
5.9 ArrayBuffer 解码
由于 http 模块的 dataReceive 回调返回的是 ArrayBuffer 类型,需要解码为字符串:
function arrayBufferToString(buffer: ArrayBuffer): string {
const uint8Arr = new Uint8Array(buffer);
let text = '';
for (let i = 0; i < uint8Arr.length; i++) {
text += String.fromCharCode(uint8Arr[i]);
}
return text;
}
这个函数将二进制数据逐个字节转换为字符。对于 UTF-8 编码内容,需要注意中文等多字节字符可能跨越两个分片,这也是 SSE 解析时需要保留不完整行的原因之一。
六、网络层配置
6.1 网络权限
在 HarmonyOS 中,应用访问网络需要在 module.json5 或 build-profile.json5 中声明网络权限。本项目需要访问 api-ai.gitcode.com 域名,配置如下:
{
"deviceTypes": ["phone", "tablet"],
"permissions": [
"ohos.permission.INTERNET"
]
}
6.2 API 鉴权
AI API 通过 Bearer Token 进行鉴权:
header: {
Authorization: `Bearer ${API_KEY}`,
'Content-Type': 'application/json',
Accept: 'text/event-stream',
}
Accept: text/event-stream 是告知服务器我们期望 SSE 格式的响应,这是 HTTP 协议中的内容协商机制。
七、状态管理与数据流
7.1 状态变量概览
项目使用了 5 个核心状态变量:
| 变量 | 类型 | 初始值 | 用途 |
|---|---|---|---|
messageList |
ChatMessage[] |
[] |
聊天历史消息 |
inputText |
string |
'' |
输入框内容 |
isLoading |
boolean |
false |
AI 是否正在回复 |
streamingContent |
string |
'' |
流式累积内容 |
errorMsg |
string |
'' |
错误提示 |
7.2 数据流时序
一次完整的"用户提问→AI 回复"流程如下:
用户点击"发送"
↓
handleSend() 被调用
↓
① inputText = '' // 清空输入框
② messageList += 用户消息 // 立即显示用户消息
③ streamingContent = '' // 重置流式内容
④ errorMsg = '' // 清除旧错误
⑤ isLoading = true // 开启加载状态
↓
queryAI() 被调用
↓
[网络请求阶段]
↓
dataReceive 触发 → onData(text) → streamingContent += text
↓ (重复多个 token)
dataEnd 触发 → onDone()
↓
⑥ messageList += AI 回复 // 完整回复加入历史
⑦ streamingContent = '' // 清空流式缓冲区
⑧ isLoading = false // 关闭加载状态
在整个过程中,ArkTS 的声明式 UI 框架自动处理了视图更新:
- 步骤②:用户消息气泡自动出现
- 步骤⑤:加载动画自动显示
- 步骤⑥→⑧:AI 消息气泡自动替换加载状态
7.3 状态更新最佳实践
ArkTS 的状态更新遵循"不可变数据"原则。当需要修改数组时,必须创建新的数组引用:
// ✅ 正确:创建新数组,触发 UI 更新
this.messageList = [...this.messageList, userMsg];
// ❌ 错误:直接修改数组,不会触发 UI 更新
this.messageList.push(userMsg);
字符串和基本类型则直接赋值即可触发更新,因为 ArkTS 会检测 @State 变量的 setter 调用。
八、错误处理体系
8.1 错误类型与处理策略
应用覆盖了三种主要的错误场景:
| 错误场景 | 检测方式 | 用户提示 |
|---|---|---|
| 网络连接失败 | err 回调参数非空 |
“请求失败: {错误详情}” |
| 服务器返回错误 | resp.responseCode !== 200 |
“服务器返回错误 (4xx/5xx): 详情” |
| 数据解析失败 | parseSSEDataLine 返回 null |
“无法解析响应: 原始数据…” |
8.2 错误提示气泡
错误信息通过 buildErrorBubble 展示为红色的聊天气泡:
@Builder
buildErrorBubble(errMsg: string) {
Column() {
Row() {
Text('⚠️ ' + errMsg)
.fontSize(13)
.fontColor('#C94A4A')
.padding(12)
.backgroundColor('#FFF0F0')
.border({ width: 1, color: '#F5C0C0' })
.borderRadius(12)
Blank()
}
}
}
红色系视觉(#C94A4A 文字 + #FFF0F0 背景)符合常见的错误提示约定,用户一眼就能识别。
8.3 异常保护
在多个关键函数中使用 try-catch 包裹可能抛出异常的操作:
// 销毁请求时忽略异常
try {
httpRequestTask.destroy();
} catch (_) { /* ignore */ }
// JSON 解析失败时静默跳过
try {
const parsed = JSON.parse(jsonStr);
// ...
} catch (_) { /* 不是合法 JSON,跳过 */ }
这种防御性编程确保了应用的稳定性,不会因为某次数据格式异常而崩溃。
九、总结与展望
9.1 项目成果
「宇宙知识AI问答」作为一个鸿蒙原生 ArkTS 应用,成功实现了以下功能:
- 完整的 AI 聊天界面:包括消息展示、输入交互、加载状态、错误处理等。
- 流式 AI 回复:基于 SSE 协议实现逐字输出,提升用户体验。
- 双模式回退:兼容不同 HarmonyOS 版本上的网络行为差异。
- 干净的架构:UI 层与服务层完全分离,代码可维护性高。
- 美学的 UI:深蓝星空主题配色,统一的设计语言。
9.2 技术亮点总结
| 亮点 | 说明 |
|---|---|
| 声明式 UI | 利用 ArkTS @State + @Builder 实现状态驱动视图更新 |
| SSE 流式 | 实现逐 token 解析,支持实时显示 AI 回复 |
| 回退机制 | 流式/非流式双模式兼容,确保稳路运行 |
| 组件化 | 7个 Builder 函数覆盖所有 UI 区域,关注点分离 |
| 防御编程 | try-catch 包裹异常操作,应用不崩溃 |
| 响应式设计 | 提供/禁用各交互元素,防止用户在加载中误操作 |
9.3 未来改进方向
功能扩展:
- 支持多轮上下文记忆的深度对话
- 添加语音输入/输出支持
- 支持图片识别(如星座图、星系照片)
性能优化:
- 消息列表虚拟滚动(大量消息时优化性能)
- 请求队列管理(多并发请求场景)
- 本地缓存(历史对话持久化)
用户体验:
- 深色模式支持
- 字体大小调节
- 消息复制/分享功能
- 回答内容中的 Markdown 渲染(数学公式、列表等)
平台扩展:
- 适配折叠屏设备
- 支持平板横竖屏布局自适应
- 鸿蒙超级终端联动
9.4 开发反思
在项目开发过程中,遇到了几个值得记录的技术挑战:
SSE 兼容性:HarmonyOS 不同版本的 http 模块对 SSE 的支持程度不同,这是最大的技术风险。通过双模式回退机制解决了这个问题。
ArkTS 数组更新:由于 ArkTS 采用引用比较检测状态变化,直接修改数组元素不会触发 UI 更新。必须创建新数组。
类型转换:ArrayBuffer 到字符串的转换在 ArkTS 中需要手动实现,通过 String.fromCharCode 逐字节转换,注意多字节字符的边界情况。
取消请求:httpRequest.destroy() 在某些情况下可能抛出异常,需要 try-catch 保护。
十、结语
「宇宙知识AI问答」不仅是一个功能完整的 AI 问答应用,更是一个展示鸿蒙原生 ArkTS 开发最佳实践的示例项目。从声明式 UI 的优雅表达,到 SSE 流式传输的实时交互,再到双模式回退的兼容性保障,每个技术决策都体现了对用户体验和代码质量的追求。
宇宙浩瀚,知识无垠。这个应用以科技的视角探索宇宙的奥秘,希望每一位使用者都能在问答中发现宇宙的壮美与科学的魅力。如果你对鸿蒙开发感兴趣,或者对宇宙知识有更多好奇,欢迎 fork 本项目,在探索代码与星辰大海的道路上一起前行。
项目地址:查看项目根目录源码,运行 hvigorw assembleHap --mode module 即可编译体验。
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)