做了个鸿蒙 App审核说我只会上传文章
于是我把它从「电子书」改成了「数据库」
顺带把六个功能模块和八个坑全拆开讲
上个月把「史鉴」提审了。111 个条目、二十多万字、792 个年份节点、57 张带出处的配图,我自己觉得挺扎实。
驳回意见只有一句话:
应用仅有历史事件人物知识文章罗列,内容丰富度欠佳,从用户实际使用需求与体验来看,目前应用可覆盖的使用场景暂时还比较有限。
翻译一下:你这不是应用,是一本电子书。
这篇不写「鸿蒙真香」,也不写「ArkTS 劝退」。只写一件具体的事:一个人、没有设计稿、没有后端、没有服务器,从空目录到提交上架,功能是怎么一块块长出来的,哪几块是被审核意见逼出来的,以及哪些坑我摔过。
看完你大概能判断:鸿蒙原生开发到底适合做什么,不适合做什么。
|
111 条目 |
23万 正文字数 |
792 年份节点 |
|
57 配图 |
12,093 行 ArkTS |
11MB 安装包 |
01
先定形态,再写第一行代码
历史类应用最容易掉进的坑,是把内容当产品。堆条目、堆字数,最后得到一本翻不到头的电子书。而电子书在应用市场是另一个类目,需要出版资质。
我后来想明白一件事:读者不缺内容,缺的是定位。你告诉他安史之乱讲了什么,他不一定需要;他真正想知道的往往是——755 年发生了什么、往前五年是什么、这条在哪个条目里能看到全貌。
所以产品定义改了一句话:不是「收录多少条」,而是「能不能在三秒内回答一个具体问题」。
这个判断会一路影响后面所有决定:为什么数据要先结构化、为什么时间轴要按年数画宽、为什么宁愿少配图也不配错图。形态定错了,后面每一行代码都在还债。
02
六个功能模块,逐个拆
每个模块按「解决什么问题 → 关键实现 → 踩到的坑」三段说。
应用结构一览
| 按年份查 | 按关键词查 | 按分类筛 |
| ▼ | ||
|
统一条目模型 同一套字段装下时代 / 朝代 / 人物 / 专题 | ||
| ▼ | ||
| 千年时间轴 | 阅读器 分节朗读 | 趣味答题 60 题 |
一份数据,三种用法——这是「工具」和「电子书」的分界线
① 数据层:一个模型装下四类内容
时代、朝代、人物、专题,四种东西长得完全不一样。用四个模型,阅读器要写四套;用四个页面,搜索要写四遍。
做法是抽一个 16 字段的统一模型 HistoryArticle:
| id / kind / name | 身份与分类 |
| subtitle / mark / accent | 副标题、标识字、主题色 |
| facts / readMinutes | 首屏速览数字、预计阅读时长 |
| lead / question | 导语、「本章要回答什么」 |
| sections | 正文 7–9 节,每节带小标题 |
| timeline | 关键节点,可解析成数值年份 |
| case / evidence / debate | 微观案例、史料依据、学界争议 |
| quotes / legacy / related | 原典摘录、遗产影响、延伸阅读 |
代价是专题和人物要塞进同一套骨架,有几节会显得勉强。收益是阅读器只有一份、搜索只有一份,加一个新条目只需要往数据文件里追一个对象。
坑:人物和朝代都要按时代筛选,但归类信息不该写进正文。所以另建了一张映射表 PERSON_PERIOD。新增人物忘了登记,筛选里就掉进「其他」。
这种隐性约定是纯数据驱动项目最容易烂的地方——它不报错,只是悄悄少一条。
② 索引层:三个入口,一个出口
三个入口都通向同一个阅读器。
按年份 输入 755、前221、公元1644 都认。先查 ±2 年,没结果自动放宽到 ±10 年,还没有就明确告诉你库的覆盖范围(约前 7000 年—20 世纪),不编假结果。
按关键词 标题、标签、速览、正文、年表、案例全部进索引,结果标注命中在哪一部分,而不是丢一堆卡片让你自己找。
按分类 朝代分上古三代、秦汉、魏晋南北朝、隋唐五代、宋辽夏金元、明清、近代七段;人物按「时代 × 身份」双筛,身份分帝王、政治、军事、思想、文学、艺文、科技、探索八类。
整次改造里性价比最高的一步,是把年表从「正文里的一段文字」变成「带数值年份的结构化数据」:
原先 870 条年表散在各条目正文里。写个脚本在构建时解析,得到 792 条 { y, label, title, articleId },覆盖率 98.3%。剩下 15 条是「唐代」「此后」这类相对时间,直接丢弃——不猜。
从这一刻起,年份才是可检索的字段,而不是文章里的一句话。
③ 时间轴:让长度自己说话
23 个政权排在一条横带上,块宽不平均分,按实际统治年数成比例:唐 289 年,秦 15 年,宽度差 19 倍。
不需要写一句「秦朝很短」,一眼就看见了。点任一政权,下方立刻列出它的关键节点,再点进条目。
实现上只有两个公式,轴范围固定在 −2100 到 1979:
left = (start - AXIS_MIN) * 100 / (AXIS_MAX - AXIS_MIN)
width = (end - start) * 100 / (AXIS_MAX - AXIS_MIN)
朝代列表页的每张卡片上也嵌了同一条微型轴,标出它在千年里的位置。卡片不再是纯文字列表,而是「能看见时间的卡片」。
④ 阅读器:一份排版、四种内容,还要能听
分节渲染,每节独立标题;四级字号(15/16/18/20,行高 26/28/32/36);朗读用 @kit.CoreSpeechKit,逐段 speak,靠 onComplete 串下一段,当前段高亮;延伸阅读跳到库里真实存在的条目。
坑一:从延伸阅读跳到另一篇,阅读器不归顶。看着像「点了没反应」,其实内容换了、滚动位置还停在上篇底部。给 articleId 加 @Watch,切换时把 scroller 归零。
坑二:同一篇自跳会和返回栈打架,变成「按返回没反应」的死循环。跳转前先判断 id 相同就直接 return。
⑤ 答题:把「读过」变成「记得」
60 题分五类(制度、人物、事件、辨析、文物),三档难度。每轮 Fisher-Yates 洗牌抽 10 题。
答对:14 个金色粒子从卡片中心四散,620ms 淡出,连击徽标跟着缩放;答错:卡片抖一下 [-9, 8, -6, 4, -2, 0],随即给解析,并标明出自哪一条,一键跳回原文。
设计上做了取舍:不做排行榜,不做积分。一个人做的 App 没有账号体系,排行榜就是假的。把反馈做在「当场」——对了有手感,错了立刻能回原文,比攒一个分数有用。
坑:粒子层放在 Stack 里写了 height('100%'),反过来把父容器撑高,整张卡片被拉变形。改成固定尺寸才正常。
题目也不是背题库。很多考的是辨析:长城是秦始皇一次修成的吗、活字印刷为什么没取代雕版、海禁是不是完全闭关。
⑥ 进度与收藏:重启还在
已读进度、收藏状态、字号偏好全部本地持久化。没有账号,不收集任何个人信息。
看着最不起眼,但审核看「使用场景」时,这正是「能长期用的工具」和「一次性浏览」的分界线之一。
03
二十多万字,是怎么堆出来的
答案是:数据即代码。12 个 .ets 数据文件,每个条目是一个对象字面量,直接编进 HAP。
好处:零请求、离线可读、搜索就是数组遍历,快。代价:内容更新要发版本。对工具型应用来说这个交换很划算。
每条统一骨架九段,写完再挂六块附件:
正文九段
导语 → 本章问题 → 国家机器 → 财政赋役 → 经济与技术 → 社会与衣食住行 → 文化观念 → 由盛转衰的机制 → 遗留影响
附件六块
关键节点年表 · 微观案例 · 原典摘录 · 史料依据 · 学界争议 · 遗产影响
不写「皇帝昏庸」,而是把赋税、兵制、土地、继承的结构性压力摊开讲。这是内容质量的底线,也是后面申诉时唯一能拿得出手的东西。
两条生成管线,都可以复跑:
▍ build_timeline_index.py 解析所有条目的年表 → 792 节点 / 23 条带 / 轴范围
▍ build_image_index.py 汇总三个图源 → 57 条图片记录
配图这块值得单独说,因为我在这里犯过最蠢的错。
最早一批图里,秦朝配了一张清代财神像,辽代配了金代器物——因为只用了关键词匹配。图片年代错配,比没有图难看得多:读者第一眼看到,会觉得整个应用都不可信。
后来定了两条必须同时满足的硬约束:
| 约束 | 说明 |
| 词边界匹配 | 馆藏 period 字段必须整词对上,Han 不能命中 Shang |
| 区间重叠 ≥ 50% | 馆藏断代区间和条目存续期重叠要过半 |
两条都不过,这条就不配图,退回主题色。宁可没有,也不拿近似图充数。
结果是 57 张图覆盖 45 个条目,剩下 66 个条目就是纯色头图。器物图来自 The Met 开放馆藏(CC0),现场照片与人物画像来自 Wikimedia Commons,每张都写明原件名、馆藏断代和作者。
人物画像要另外处理:Commons 关键词搜索合格率不到一半,会搜出同名运动员、月球环形山、故居门楼、邮票。办法是把候选图渲成一张联络表,逐张人眼看,能用的才入库。
这一步没有捷径。算法能帮你筛掉 60%,剩下的 40% 只能自己看。
04
上架材料:六样东西,少一样都过不去
| 项目 | 要求 | 容易踩的坑 |
| 图标 | 1024 或 216 方形 PNG,≤3MB | 分级图标要分层素材,不是一张图 |
| 截图 | 竖 1080×1920 或横 1920×1080,3–10 张 | 真机 1216×2688 直接缩会变形 |
| 名称 | ≤15 汉字,与软著完全一致 | 17 字标题只能放副标题/推荐位 |
| 简介 | 一句话 ≤80 字 | 别写成内容清单,要写能干什么 |
| 资质 | 软著、备案、隐私政策、隐私权利 | 隐私政策必须是公网可访问的 URL |
| 签名 | 发布证书 + 发布 Profile | 发布包装不进自己手机,见下一节 |
一个具体教训:17 字标题。华为 AGC 的「应用名称」上限是 15 个汉字,所以 17 字标题不能填在名称里,只能放副标题或推荐位。
而且中文标题的字数必须用 len() 数——「一百一十一」是 5 个字,我第一版数成 4 个,整条标题就差一位。
截图也不是截完就完事。我的做法是写一个排版脚本:真机截图作为素材,脚本负责按比例缩放、加设备边框、排标题文案,一次同时出竖屏 6 张和横屏 6 张。改文案重跑 40 秒,比在图片软件里手动改 12 张快得多。
05
签名:最容易卡住的一步
DevEco Studio 的自动签名用的是 debug 证书,能装机,不能上架。
换成发布证书之后,hdc install 直接报错:
9568322 · not trusted app source
这不是配置错了,是机制如此:发布签名只信任应用市场这个渠道,不信任本地直装。
所以流程要反过来:
▍ 日常调试用 debug 签名,DevEco 自动生成
▍ 出包前切发布签名,只看 assembleHap 能不能过
▍ 真机验证一律用 debug 包
还有个更隐蔽的:debug 证书的密码是 DevEco 用本机密钥加密的一串字符,拷到另一个工程会直接报 Signature material verification failed。不能复用——换机器、换工程,重新生成一次自动签名就好。
我第一次遇到时以为是证书坏了,折腾了两个小时。
06
被驳回之后,我改了什么
先判断这条意见在说什么。它不是在说条目不够多——继续加文章解决不了这个问题。它说的是应用形态:用户只能在列表里点开文章往下读,没有一件可以「完成」的事。
所以做了五件事:
| # | 动作 | 为什么 |
| 1 | 改分类 | 从「图书阅读」改「教育 > 学习工具」。分类决定审核员用什么标准衡量你 |
| 2 | 改介绍开头 | 不再写「收录 111 个条目」,改成「能查、能读、能听」 |
| 3 | 换首图 | 第 1 张从首页换成时间轴页,第一眼看到查询与条带 |
| 4 | 加工具 | 年表抽成结构化数据,加年份检索、千年条带、趣味答题 |
| 5 | 写申诉 | 逐条对应:6 项能力、7 类场景、4 个后续版本 |
一句话总结这段经历:
与其争「我的内容很多」,不如证明「用户能在这里完成一件事」。
这条经验对所有内容型应用大概都通用:审核看的不是你写了多少,而是用户能做什么。
07
ArkTS 踩坑清单,八条
| 现象 | 原因 | 改法 |
| Path 画的图标在 3x 屏小得看不见 | commands 按 px 解析,24 单位在 3x 屏只有 8vp | 换 SVG + fillColor() |
| 筛选标签点了不亮 | @Builder 按值传参,状态变了不重建 | 抽成 @Component + @Prop |
| 答题页按返回直接退出应用 | 详情层与答题层平级,返回键先关答题 | Stack 叠层 + onBackPress 里详情优先 |
| 粒子层把整张卡片撑变形 | Stack 内 height('100%') 反向撑开父容器 | 固定 250×150 |
| Alignment.CenterStart 编译报错 | 没这个枚举 | Alignment.Start |
| 自动点击总点偏 | hdc 坐标是物理像素,不是逻辑像素 | uitest dumpLayout 取 bounds |
| 名字被压成一个字 | 和旁边的标签挤在一行 | name 独占一行 width('100%') |
| 发布包装不进手机 | 9568322 · not trusted app source | 真机验证用 debug 包 |
这八条里,前五条都不是「难」,是「反直觉」。文档里不会写,只能自己撞一次。
现在这个应用:111 个条目、23 万字、792 个年份节点、57 张带署名的图、60 道题,HAP 11MB,纯本地,只有一条联网权限——在线语音合成。
第二次提审在等结果。如果又被打回,我大概还会继续改形态,而不是继续堆内容。
一个人做应用,最大的优势是可以随时推翻自己;最大的风险是把力气花在了没人反对的地方。
审核意见难听,但它至少是一份免费的、具体的、来自真实用户视角的反馈。这话说起来有点阿 Q,但确实省了我几个月。
更多推荐

所有评论(0)