做了 ToolMaster 这款纯离线的 HarmonyOS NEXT 开发者工具箱之后,很多朋友问我最多的一句话是:“现在 App 不联网还能算工具吗?”我的回答很直接:开发者工具恰恰不应该联网。我在鸿蒙原生应用上用 ArkTS 从零搭了一个免权限、零上传、所有内容都在本机完成的工具箱,把 JSON 格式化、Base64 编解码、时间戳转换、哈希计算、正则测试、URL 编解码这些高频功能全部收进 App 里,不申请任何网络权限,不做埋点,不弹广告,打开就能用。整个项目从设计到上架遇到的坑,这篇博文一次讲透。
1. 项目定位:为什么要在 HarmonyOS NEXT 上做一个纯离线工具箱
1.1 从“什么都联网”到“什么都本地”
做工具箱之前,我先列了一张“烦人清单”,把日常开发中需要反复打开网页、切换聊天窗口、找在线工具站的场景全部写了下来:JSON 想格式化但日志太长、接口返回的 Base64 图片想临时解出来看一眼、时间戳要转日期却发现忘了打开网页、给文件算个 MD5 还得装一个巨大的桌面软件。
这些场景有两个共同点:第一,每次操作的数据量都不大,但发生频率极高;第二,数据很可能涉及接口报文、密钥、调试参数,属于不该出现在公网上的内容。过去用网页工具图方便,但你把一段内部接口的 JSON 塞进一个免费网站时,等于默许它存到对方服务器上。对于企业开发者,这甚至可能触发信息安全问题。
HarmonyOS NEXT 这个平台恰好适合做这件事。它的 ArkTS 语言和 ArkUI 声明式框架虽然和 Android、iOS 都不一样,但系统自带的 util 工具库、加密框架、持久化能力已经足够覆盖一个工具箱 90% 的需求。加上 NEXT 的权限审核很严格,纯离线应用不申请任何权限,上架时省心,用户使用时也放心。
1.2 工具清单的选型标准
工具不是越多越好,维护一个臃肿的“全家桶”反而会拖垮体验。我的选型标准有三条:第一,必须能纯本地完成,不依赖云端计算;第二,操作链路要短,最好从进入到拿到结果不超过两次点击;第三,有明确的高频使用场景,而不是为了凑数量。
按这套标准,第一版 ToolMaster 落地了六类工具:JSON 格式化、Base64 编解码、时间戳转换、哈希计算(MD5/SHA-1/SHA-256)、正则表达式测试、URL 编解码。后来又加了一个收藏夹机制,用户可以把常用的工具固定在首页头部。
有人建议我加“二维码生成”“颜色选择器”“ASCII 码表”这类功能,我评估后的结论是:二维码生成放到第二版,因为它引入的绘制逻辑会显著增加包体,而且扫码场景在调试中占比并不高。工具类 App 宁可少而精,也不要让用户在一堆低频功能里找不到真正需要的入口。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 整体架构与工程搭建
2.1 项目结构与模块划分
先用 DevEco Studio 创建一个空应用(Empty Ability),选择 Stage 模型。Stage 模型是 HarmonyOS NEXT 的主流形态,它的 isolation 机制天然适合工具类应用——每个 UIAbility 独立运行,不会互相干扰。
工程结构我按“功能聚合”而不是“文件类型”来组织。六个工具各自放在一个独立的工具类文件里,UI 层只负责调用,不写业务逻辑。当前版本的目录大概是这样的:
code复制entry/src/main/
├── ets/
│ ├── entryability/
│ │ └── EntryAbility.ets
│ ├── pages/
│ │ ├── Index.ets // 工具网格首页
│ │ ├── JsonToolPage.ets
│ │ ├── Base64ToolPage.ets
│ │ ├── TimestampToolPage.ets
│ │ ├── HashToolPage.ets
│ │ ├── RegexToolPage.ets
│ │ └── UrlToolPage.ets
│ └── common/
│ ├── json_util.ets // JSON 格式化核心逻辑
│ ├── base64_util.ets // Base64 编解码
│ ├── hash_util.ets // 哈希计算
│ ├── storage_util.ets // Preferences 封装
│ └── types.ets // 全局类型定义
├── resources/
│ ├── base/element/color.json
│ ├── base/element/string.json
│ └── base/media/
└── module.json5
这里有个设计上的取舍:为什么每个工具都单独拆一个 Page 而不是做成单个页面内切换 Tab?因为工具类 App 的状态管理讲究简单直接,每个工具页的输入框、结果区、历史记录都是独立的,如果在一个页面里用 Tabs 包裹六个工具,状态回收和页面缓存都会变复杂。拆成独立页面后,配合 Navigation 的路由跳转,每个页面只管自己的事情,代码可维护性高很多。
在 types.ets 里我定义了所有工具页面共用的数据结构,比如历史记录统一用一个 HistoryItem:
typescript复制export interface HistoryItem {
toolId: string;
input: string;
output: string;
timestamp: number;
}
这个类型定义后来帮了
