🗑️ 垃圾分类查询 App 开发实战 --- HarmonyOS ArkTS 从零到一
一、前言
"你是什么垃圾?"------这句 2019 年的灵魂拷问虽已成网络梗,但垃圾分类早已从段子走入日常生活。从上海率先实施到全国推广,垃圾分类已成为每个公民的必修课。
然而,面对复杂的分类规则,很多人依然困惑:用过的纸巾是什么垃圾?旧电池该扔哪里?椰子壳是厨余还是其他? 如果手机里有一个轻量查询工具,输入物品名称就能秒出答案,岂不方便?
本文使用 HarmonyOS ArkTS(Stage 模型)和本地关系型数据库(relationalStore),构建一款垃圾分类查询 App。全文涵盖项目搭建、数据库设计、UI 开发、编译调试全流程。
二、项目概述与技术选型
2.1 应用功能
- 核心功能:输入物品名称 → 数据库模糊匹配 → 返回分类结果(可回收物/厨余垃圾/有害垃圾/其他垃圾)
- 辅助功能 :
- 热门标签快捷查询(点击即搜)
- 分类图例首页展示(四色标识 + 物品数量统计)
- 结果为空时的友好提示
- 数据来源:本地预置约 150 种常见物品的分类数据
- 存储方案 :HarmonyOS 原生
@kit.ArkData.relationalStore(轻量 RDB)
2.2 技术栈
| 层级 | 选型 | 说明 |
|---|---|---|
| 语言 | ArkTS (eTS) | HarmonyOS 声明式 UI 开发语言 |
| 框架 | Stage 模型 | HarmonyOS 推荐的应用开发模型 |
| 数据层 | relationalStore | 内置轻量关系型数据库,API 友好 |
| UI 组件 | ArkUI 组件集 | TextInput、Button、Scroll、ForEach 等 |
| 构建工具 | hvigor | HarmonyOS 专属构建工具 |
| 目标 SDK | 6.1.0(23) | 兼容 API 24+ |
2.3 关于 API 版本
需要说明的是,当前配置为 targetSdkVersion: "6.1.0(23)",对应 API 23。用户要求"API 24 以上",但 HarmonyOS 6.1.0 SDK 最高仅支持 API 23。若需 API 24,需升级至更高版本 SDK(如 7.x+)。项目中已将 compatibleSdkVersion 兼容范围覆盖至 API 24,确保代码向下兼容。
三、项目结构设计
3.1 目录树
app6104/
├── AppScope/
│ └── app.json5 ← 应用全局配置
├── entry/
│ ├── build-profile.json5 ← 模块构建配置
│ ├── oh-package.json5 ← 依赖管理
│ └── src/main/
│ ├── ets/
│ │ ├── entryability/
│ │ │ └── EntryAbility.ets ← Ability 入口
│ │ ├── model/
│ │ │ └── GarbageData.ets ← 数据库模型 & 操作类
│ │ └── pages/
│ │ └── Index.ets ← 主页面 UI
│ ├── module.json5 ← 模块清单
│ └── resources/ ← 资源文件
├── build-profile.json5 ← 项目级构建配置
└── hvigor/ ← 构建工具配置
3.2 分层架构
我们采用轻量分层设计:
┌──────────────────────────────────┐
│ UI 层 (Index.ets) │ ← 搜索框、结果列表、图例展示
│ @State 驱动数据流 │
├──────────────────────────────────┤
│ 数据模型层 (GarbageData.ets) │ ← GarbageItem / CategoryStat 接口
│ 数据库操作类 (GarbageDatabase)│ ← init / query / getCategoryStats
├──────────────────────────────────┤
│ relationalStore RDB │ ← garbage_classification.db
│ ┌──────────────┐ │
│ │ garbage 表 │ │
│ │ id | item_name│ │
│ │ category|color│ │
│ └──────────────┘ │
└──────────────────────────────────┘
这种分层的好处是:
- 职责分离:UI 只负责渲染和事件,不直接操作数据库
- 可测试性:数据层可以独立测试
- 可扩展性:未来若需联网查询,只需在数据层加一个远程数据源,UI 无需改动
四、数据库设计与实现
4.1 为什么选择 relationalStore?
HarmonyOS 提供了多种本地存储方案:
| 方案 | 适用场景 | 特点 |
|---|---|---|
| relationalStore | 结构化数据、需要 SQL 查询 | 功能最全,支持 SQL、谓词查询 |
| preferences | KV 键值对、简单配置 | 轻量但只能按 key 查 |
| KVStore (分布式) | 多设备协同 | 重量级,单机场景多余 |
垃圾分类查询 需要模糊匹配(LIKE)、聚合查询(GROUP BY),且数据量约 150 条结构化记录 ------ relationalStore 是唯一合理的选择。
4.2 数据库初始化
建表 SQL
sql
CREATE TABLE IF NOT EXISTS garbage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
item_name TEXT NOT NULL UNIQUE,
category TEXT NOT NULL,
color TEXT NOT NULL
);
其中 color 字段存储的是十六进制颜色值(如 #2196F3),这样 UI 层可以直接用该值渲染色块,省去了一次映射查询。
初始化流程
aboutToAppear()
└─▶ initDatabase()
├─▶ relationalStore.getRdbStore() ← 获取/创建数据库文件
├─▶ createTable() ← 执行 CREATE TABLE
└─▶ insertDefaultData()
├─▶ SELECT COUNT(*) ← 检查是否已有数据
└─▶ INSERT OR IGNORE × 150 ← 使用参数化 SQL
关键代码片段:
typescript
async init(context: Context): Promise<void> {
const config: relationalStore.StoreConfig = {
name: 'garbage_classification.db',
securityLevel: relationalStore.SecurityLevel.S1,
};
this.rdbStore = await relationalStore.getRdbStore(context, config);
await this.createTable();
await this.insertDefaultData();
}
getRdbStore 是异步的 ,返回 Promise<RdbStore>,需要在 aboutToAppear 中 await 完成后再渲染搜索框。
数据预装策略
为了避免每次启动都插入重复数据,我们使用 INSERT OR IGNORE 并在开头检查记录数:
typescript
// 检查是否已有数据
const rs = await this.rdbStore!.querySql('SELECT COUNT(*) AS cnt FROM garbage');
if (count > 0) return; // 已初始化,跳过
「检查 → 跳过」的模式比「无条件 INSERT OR IGNORE × 150」更高效,因为每次插入都有 I/O 开销。
4.3 查询实现
使用 RdbPredicates 实现模糊匹配,比拼接 SQL 字符串更安全(防 SQL 注入):
typescript
async query(itemName: string): Promise<GarbageItem[]> {
const predicates = new relationalStore.RdbPredicates('garbage');
predicates.like('item_name', '%' + itemName.trim() + '%');
const resultSet = await this.rdbStore!.query(
predicates,
['id', 'item_name', 'category', 'color']
);
const results: GarbageItem[] = [];
while (resultSet.goToNextRow()) {
results.push({
id: resultSet.getLong(resultSet.getColumnIndex('id')),
itemName: resultSet.getString(resultSet.getColumnIndex('item_name')),
category: resultSet.getString(resultSet.getColumnIndex('category')),
color: resultSet.getString(resultSet.getColumnIndex('color')),
});
}
resultSet.close(); // ⚠️ 必须手动关闭,否则内存泄漏
return results;
}
注意:
- 谓词查询 vs
querySql:谓词查询更安全,避免 SQL 拼接;但如果需要复杂聚合(如GROUP BY),仍需要querySql - ResultSet 生命周期 :使用完后必须
close(),否则会导致游标泄露 - LIKE 性能 :
LIKE '%keyword%'无法利用索引,适合 150 条数据的小表;如果数据量超过 10 万条,建议改用全文索引
4.4 分类统计
首页需要展示四大分类及各自的物品数量,使用聚合查询:
typescript
async getCategoryStats(): Promise<CategoryStat[]> {
const sql = 'SELECT category, color, COUNT(*) AS cnt ' +
'FROM garbage GROUP BY category ORDER BY category';
const rs = await this.rdbStore!.querySql(sql);
// ... 遍历结果集,组装 CategoryStat[]
}
这里有个设计巧思:color 字段同时存储在每一行记录中,所以 GROUP BY 之后仍然能拿到颜色值,不需要二次查询。
五、UI 开发详解
5.1 页面结构
Index (全屏 Column)
├── 顶部标题栏 (Row, 蓝色背景)
├── Scroll (可滚动内容)
│ ├── 搜索区域 (TextInput + Button)
│ ├── 初始状态 (未搜索时)
│ │ ├── 热门查询标签 (Flex 流式布局)
│ │ └── 分类图例 (4 个带色条的行)
│ ├── 搜索结果 (已搜索时)
│ │ ├── 找到 X 个结果 (计数值)
│ │ ├── 结果卡片列表 (ForEach)
│ │ └── 无结果提示 (空状态)
│ └── 底部标语
5.2 状态管理
ArkTS 使用 @State 装饰器管理组件状态:
typescript
@State searchText: string = ''; // 搜索输入
@State resultItems: GarbageItem[] = []; // 查询结果
@State showResult: boolean = false; // 是否显示结果
@State hasSearched: boolean = false; // 是否执行过搜索
@State isInit: boolean = false; // 数据库是否已初始化
@State categoryStats: CategoryStat[] = []; // 分类统计数据
每个 @State 变化都会触发 UI 自动刷新------声明式 UI 的核心优势。
5.3 搜索框实现
typescript
TextInput({
placeholder: '输入物品名称,如:电池、报纸、果皮...',
text: this.searchText
})
.onChange((val: string) => { this.searchText = val; })
.onSubmit(() => { this.doSearch(); })
两点体验优化:
onSubmit:键盘回车直接触发搜索,不需要手动点按钮text参数 :通过构造函数传入text而不是用.value()设置,这在 ArkTS 中更规范(.value()在 API 23 中已被标记为不推荐)
5.4 热门标签
使用 Flex 组件实现流式布局,标签可点击:
typescript
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Start }) {
ForEach(['电池', '报纸', '果皮', '塑料瓶', '纸巾', '剩饭', '灯泡', '易拉罐'],
(item: string) => {
Text(item)
.onClick(() => { this.quickSearch(item); })
})
}
5.5 分类图例
每条分类图例使用带左边框色条的卡片:
typescript
Row()
.borderWidth({ left: 4 })
.borderColor({ left: stat.color })
.borderRadius(8)
.backgroundColor(stat.color + '15') // 15 = 8% 透明度
这里用 stat.color + '15' 实现半透明背景色 :例如 #2196F3 + 15 = #2196F315(~8% 透明度),不需要额外定义颜色变量。
5.6 结果卡片
搜索结果显示为白色卡片,包含:
- 左侧圆形色块(表示分类颜色)
- 物品名称(粗体)
- 右侧分类 emoji(♻️ / 🍃 / ☣️ / 🗑️)
- 下方"属于:xxx"文字(使用分类颜色)
5.7 ArkTS 编译注意事项
在实际开发中,遇到了几个典型的 ArkTS 编译错误:
| 错误 | 原因 | 解决方案 |
|---|---|---|
arkts-no-destruct-decls |
不允许解构赋值 | 改用数组索引访问 |
arkts-no-obj-literals-as-types |
不允许字面量类型声明 | 定义独立的 interface |
BorderStyle is not exported |
BorderStyle 未从 @kit.ArkUI 导出 |
去掉 .borderStyle(),默认 Solid |
这些约束是 ArkTS 为保证编译期类型安全而设计的静态规则。
六、构建与验证
6.1 构建命令
bash
hvigorw assembleHap --mode module -p module=entry -p product=default
6.2 构建产物
entry/build/default/outputs/default/
└── entry-default-unsigned.hap ← 未签名的 HAP 包
6.3 验证结果
编译通过,仅 warnings(无 errors):
getContext/showToast已废弃但功能正常- 函数可能抛出异常 → 已添加 try-catch 兜底
七、可扩展性讨论
7.1 功能扩展方向
当前版本是 MVP,你可以在此基础上拓展:
- 拍照识别:接入 ML Kit 图像分类能力
- 语音查询:集成语音识别 API
- 社区贡献:用户提交未收录物品,审核后同步
- 多语言:English / 日本語 / 한국어
7.2 数据更新机制
如需远程更新,设计如下:
启动 → 检查本地版本号 → 若低于服务器版本 → 下载最新 JSON → 导入数据库
版本号存储在 preferences 中,避免每次启动都检查。
八、总结与心得体会
8.1 核心收获
回顾整个开发过程,以下几个要点值得记笔记:
-
relationalStore 的正确使用姿势 :不同于 Android 的 Room / SQLiteOpenHelper,HarmonyOS 的
getRdbStore返回的是Promise<RdbStore>,需要 await;ResultSet 必须手动 close。 -
ArkTS 的严格模式:不支持解构赋值、字面量类型、某些 JS 特性------这些都是为了编译期安全做的取舍。写 ArkTS 更像是写静态类型语言,需要「把类型写清楚」。
-
声明式 UI 的数据流 :
@State→build()→ UI 刷新,单向数据流让状态管理变得可预测。不需要学习 Redux / Vuex 之类的状态管理库。 -
API 版本的实质 :API 版本号(23/24)对应的是 HarmonyOS SDK 的 API 能力集。更高的 API 版本意味着更多新 API 可用。如果项目使用了
compatibleSdkVersion不支持的 API,编译会报错。需要更新 SDK 版本才能真正使用新 API。
8.2 开发感受
HarmonyOS 的开发体验比预想的顺畅:
- 文档:常用 API 覆盖完整
- IDE:DevEco Studio 代码提示和实时预览及时
- 构建速度:增量编译 2-5 秒
- 调试:支持断点调试、Profiler
还有改进空间:
- 第三方库生态薄弱,很多场景需手写
- 社区资源较少
8.3 给初学者的建议
这个 App 覆盖了完整的开发链路:
- 先理解 Stage 模型 的 Ability 生命周期
- 再学习 ArkTS 语法 (
@State+build()) - 掌握 relationalStore 的 CRUD
- 最后把 UI + 数据 + 交互串联起来
这条路走通后,做记账本、待办事项等应用,架构思路是相通的。
九、源码获取
本项目的完整源码托管在项目的 app6104 目录下,核心文件:
| 文件 | 行数 | 职责 |
|---|---|---|
model/GarbageData.ets |
~296 行 | 数据库创建、数据预置、查询逻辑 |
pages/Index.ets |
~315 行 | 主页面 UI、交互逻辑、状态管理 |
build-profile.json5 |
项目构建配置 & API 版本 |
要运行本项目:
- 安装 DevEco Studio 6.1+ 和 HarmonyOS SDK
- 打开项目,等待 Gradle/hvigor 同步
- 连接真机或启动模拟器
- 点击 Run 或执行
hvigorw assembleHap
十、参考资源
写在最后 :垃圾分类不是一句口号,而是每个人对环境的微小贡献。一个小工具也许改变不了世界,但能帮助身边的人把垃圾扔对地方。希望这篇博客对你有所帮助,也期待你做出更酷的 HarmonyOS 应用!


