资讯动态

HarmonyOS 4刷题APP开发:Stage模型、ArkUI与RDB持久化实践

发布时间:2026/9/11 23:05:34 来源:尧图企业网站定制
简介本代码包是一款基于HarmonyOS 4开发的刷题型鸿蒙应用完整工程面向正在学习鸿蒙开发或需要完成毕业设计、期末大作业的开发者。项目围绕HarmonyOS基础架构、分布式任务调度、UI框架与组件、DevEco Studio工程配置等核心知识展开通过真实刷题场景演示了从题目列表、答题页到结果统计的完整交互流程并接入云存储模块适合对照源码理解HarmonyOS API调用与跨设备数据管理思路。包内共1793个文件约5.35MB主体为js/ts/ets源码文件、pem证书、json/json5配置文件以及png/svg图标和md说明文档目录结构清晰包含答题页、结果页、云存储等模块便于按功能快速查找。已有611人浏览学习。借助该工程可快速复现刷题应用的数据存储、题目展示与答题交互逻辑是快速上手鸿蒙4开发、课设与项目答辩的实用参考。1. 刷题APP是HarmonyOS 4最能“所见即所得”的入门实践如果把一份基于 HarmonyOS 4 开发的刷题APP源代码压缩包拿到手里第一反应别是去找“运行说明.txt”而是先把它当作一个普通Stage工程来拆。刷题类应用的典型路径是题库从本地JSON或数据库读出页面在列表和答题之间切换选项点击后判分并记录错题。这个骨架几乎覆盖了鸿蒙原生开发里最常考的四个能力ArkTS类型系统、ArkUI响应式渲染、关系型数据库持久化、页面路由。这个所谓“高分项目”通常不是靠算法而是靠工程完成度拿分。下面要做的是把这条路径重走一遍让拿到源码的从业者或正在备考HarmonyOS应用基础认证的开发者能在真机上看到一个可运行的刷题闭环而不是停在解压页面。2. HarmonyOS 4刷题APP的工程骨架与Stage模型选型一份能上真机跑的鸿蒙刷题APP源代码工程结构基本都围绕Stage模型展开。可能有些从2023年之前模板生成的老代码还在用FA模型那时页面和Ability绑定得很紧页面跳转像是切进程。HarmonyOS 4的DevEco Studio新建项目已经默认推荐Stage模型刷题APP选它不只是为了“新”更重要的是它能让一个应用只有一个UIAbility题库列表、答题页、错题本这些页面都在同一个窗口内用路由切换答题做到一半去查知识点再回来时当前进度不会被系统当作后台任务回收。2.1 为什么刷题APP优先选Stage模型而不是FA模型FA模型里一个页面可以对应一个Ability切换页面等于切换Ability页面之间的全局状态很难维护Stage模型统一由UIAbility承载窗口页面路由通过main_pages.json和路由API完成。刷题APP里的“章节练习”“答题页”“错题本”只是同一个UIAbility内的不同页面这样状态不会被系统频繁回收。从开发体验看Stage模型还提供了AppStorage和PersistentStorage刷题进度这种全局数据可以直接挂到全局存储上不用自己写单例工具类。这给答题页和结果页的数据共享省了很多事。对答辩更友好的一个理由FA模型在系统资源紧张时Ability可能会被回收而Stage模型的应用重启后可以通过SavedState恢复页面刷题做到一半切走再回来当前题目还能留住。这里也有一个容易误判的点有人看到源代码里没有“第二入口”就怀疑不是Stage模型。实际上Stage模型不一定只有EntryAbility刷题项目一般一个UIAbility就够。真正要判断模型的方法是看有没有module.json5以及abilities数组里的srcEntry指向是不是ets/entryability/EntryAbility.ts。2.2 读懂module.json5与main_pages.json这两个入口配置拿到源代码以后我一般先看三个文件AppScope/app.json5、entry/src/main/module.json5、entry/src/main/resources/base/profile/main_pages.json。在源码包被解压、导入IDE之前先确认这三个文件是否存在能帮你判断这个项目是完整工程还是只有部分源码。module.json5里的abilities数组负责声明Ability但页面列表不在这里而是在main_pages.json。刷题APP的main_pages.json通常长这样{ src: [ pages/Index, pages/QuestionBank, pages/ExamPage, pages/ResultPage ] }这里src数组的顺序决定的是页面路由能否直接通过url访问渲染时并不一定按数组顺序展示。刷题APP一般把Index作为首页把ExamPage注册成答题页这样router.pushUrl({ url: pages/ExamPage })才能找到对应页面。常有人把main_pages.json删了导致路由跳转报错page not found这个文件不是装饰是路由表。2.3 用DevEco Studio导入“高分项目”zip的最小步骤提示如果是别人打包的zip先确认压缩包第一层是AppScope还是entry。如果是后者解压后把外层文件夹指给DevEco即可不要新建工程再拷贝源码。导入步骤可以压缩成三行打开DevEco Studio选择File Open选择解压后的工程目录而不是zip本身。等待ohpm install自动同步依赖如果没有自动同步在菜单Tools Ohpm里执行ohpm install。打开entry/src/main/ets/pages/Index.ets先点Previewer看首页渲染再连真机或模拟器运行。如果工程是从旧版本SDK创建导入后会提示升级hvigor或SDK这里建议点“稍后”而不是直接升级因为升级hvigor经常把build-profile.json5里的配置一起改了导致一堆签名错误。等到能跑通一遍真机再考虑版本升级。整个Stage模型对刷题这种单一入口应用来说其实有点“重”但它提供的AppStorage和PersistentStorage能省去自己造全局变量的麻烦。这里的选型逻辑可以用一个表讲清楚维度FA模型Stage模型页面跳转Ability之间绑定地址路由或Navigation全局状态靠DataAbility或外部存储AppStorage/Environment本地数据文件偏好RDB/Preferences代码组织页面与Ability耦合分层清晰易改3. 题库数据建模与本地持久化从JSON到关系型数据库刷题APP的源代码里最值得自己改的部分是题库数据。很多“高分项目”把题库做成几千行JSON塞在rawfile里运行时不切实际另一种做法是首次启动时把JSON导入RDB之后所有查询都走SQL。这个设计决定了答题体验5000道题的库如果每次全部装入内存翻页时肉眼可见卡顿而RDB查询只在当前章节取50道题内存占用会稳定很多。3.1 用TypeScript定义刷题数据模型ets文件里可以直接使用TypeScript接口一个选择题最少要有题目、四个选项、正确答案、章节标签和解析。下面是我常用的模型export interface Question { id: number; category: string; type: single | multiple | judge; content: string; options: string[]; answer: number[]; // 单选时只有1个元素多选是多个下标 explanation: string; difficult: 1 | 2 | 3; // 1简单2中等3难 }answer用number[]而不是number是因为判断题和单选题可以统一成“下标数组”避免为单选、多选各写一套判分逻辑。type字段用字符串而不是布尔值是为了后续加入“看题模式”和“考试模式”时能直接判断题型。解析接口字段用explanation很多项目写成analysis虽然不影响运行但会给错题本页面读取增加一次字段映射建议统一命名。3.2 RDB建表与索引设计使用鸿蒙的关系型数据库需要先获取RdbStore然后建表。以一次建两张表为例CREATE TABLE IF NOT EXISTS question ( id INTEGER PRIMARY KEY AUTOINCREMENT, category TEXT NOT NULL, type TEXT NOT NULL, content TEXT NOT NULL, options TEXT NOT NULL, answer TEXT NOT NULL, explanation TEXT, difficult INTEGER DEFAULT 1 ); CREATE INDEX idx_question_category ON question(category); CREATE INDEX idx_question_type ON question(type);options和answer用TEXT保存其中options存JSON字符串answer存[1]这样的字符串。或者使用RDB结果转换时再展开。这里需要说明刷题APP每次答题只需要当前章节数据category上的索引会让where category ?非常快不要为content加索引因为长文本索引既占空间又对like查询没有明显收益。在EntryAbility的onWindowStageCreate前后初始化RdbStore用RdbPredicates查询。代码较长但核心流程是这样的import { relationalStore } from kit.ArkData; const config: relationalStore.StoreConfig { name: quiz.db, securityLevel: relationalStore.SecurityLevel.S1 }; relationalStore.getRdbStore(this.context, config, (err, store) { store.executeSql(CREATE TABLE IF NOT EXISTS question (...)); });securityLevel是HarmonyOS必须设的一个参数刷题这种没有通信敏感数据的应用用S1即可设成S3会导致加密逻辑更慢真机调试时每次冷启动变长。StoreConfig里还能指定encrypt但普通刷题项目不必开启。3.3 第一次启动时把rawfile里的JSON导入RDB“高分项目”里通常带一个resources/rawfile/questions.json。要把它变成可查询的数据需要在首次启动时读取并循环插入。这里不建议在主线程一条条insert5千条数据会让白色启动页多停留2秒以上。常见做法是用ohos.resourceManager的getRawFileContent读取questions.json。在一个新创建的RdbStore事务里批量插入。用Preferences写一个db_initialized标记避免下次启动重复导入。导入完成后错题本、章节练习、随机刷题三个页面都从这张question表取数。这样代码的可维护性比“直接改JSON”高很多。真正的坑在于getRawFileContent拿到的是Uint8Array需要先转成字符串再用JSON.parse。很多源代码项目在这里直接把二进制对象当字符串用导致运行时不报错但题库是空的。关键参数说明executeSql方法只接受SQL文本不参与参数绑定需要动态参数时用store.insert或RdbPredicates。表结构变更时需要先执行ALTER TABLE或者卸载应用重新跑这个在开发阶段可以省事但交付前不要这样。4. 用ArkUI实现刷题页面与答题交互刷题APP在UI层面的核心是答题页。一个答题页要处理三件事题目展示、点击选项、判分后跳到下一题。HarmonyOS 4的ArkUI用Component和State实现响应式界面代码模式可以统一成一个State保存当前题目索引一个State保存用户选择一个计算属性返回是否正确。这个页面的状态清晰了后面的错题本才能拿到需要的数据。4.1 从题库列表页路由到答题页并携带参数用router.pushUrl传参时参数长度有限制一般只传题库ID和章节名不要传整个题目对象。示例import { router } from kit.ArkUI; router.pushUrl({ url: pages/ExamPage, params: { category: this.currentCategory, startIndex: this.startIndex } }).then(() { console.info(push exam page success); }).catch((err: Error) { console.error(push failed, code is ${err.code}, message is ${err.message}); });这里的params会在ExamPage的onPageShow或aboutToAppear里用router.getParams()接收。接收后要立刻深拷贝一份因为router.getParams()拿到的对象与源页面可能共享引用在ArkUI里页面被回收时参数引用行为会变建议用结构化克隆。4.2 用State和ForEach渲染选项与选中反馈答题页的State selected: number[]用来记录本次选择State current: number记录当前是第几题。页面主体用一个Column包含题干和选项Builder OptionButton(option: string, index: number) { Button(option) .width(100%) .backgroundColor(this.selected.includes(index) ? #0A59F7 : #FFFFFF) .fontColor(this.selected.includes(index) ? #FFFFFF : #182431) .onClick(() this.toggleSelect(index)); }整个列表用ForEach(this.currentQuestion.options, (option: string, index: number) ...)渲染。注意ForEach的第二个参数会给到两个参数第一个是元素第二个是索引不要反过来写。这也是很多源代码项目运行后点击任一项都选中第一项的原因。toggleSelect里要区分单选和多选toggleSelect(index: number) { if (this.currentQuestion.type single || this.currentQuestion.type judge) { this.selected [index]; } else { const pos this.selected.indexOf(index); if (pos -1) { this.selected.splice(pos, 1); } else { this.selected [...this.selected, index]; } } }这里splice直接改数组是能触发界面刷新的因为this.selected是被State观察的引用重新赋值会整体刷新但为了保险多选时我用展开运算符生成新数组再赋值避免数组内部修改后状态检测不到的问题。这个机制在HarmonyOS 4的State里其实通过代理已经处理但写成不可变更新风格能少踩一半坑。4.3 倒计时与自动交卷的实现考试模式需要一个倒计时用setInterval在aboutToAppear里启动、在aboutToDisappear里清掉this.timerId setInterval(() { this.remainSeconds--; if (this.remainSeconds 0) { clearInterval(this.timerId); this.submitPaper(); } }, 1000);remainSeconds是State每秒自减一次界面上的进度条会自动推进。这里有一个很重要的参数setInterval返回的类型在TS里是number在ArkTS里建议定义成number类型不要用NodeJS.Timer或ReturnTypetypeof setInterval因为ETS编译器对后者的类型定义经常报错。submitPaper()里要同时处理三件事计算得分、写入答题记录、跳转结果页。得分计算不要循环两次可以在用户选择题目的同时就维护一个Recordnumber, number[]交卷时只需要和答案做一次对比。状态变量作用刷新时机State current当前题目在列表中的索引点击“下一题”后自增State selected当前题目的选中项列表点击选项时重新赋值State remainSeconds剩余秒数setInterval每秒触发Prop category父组件传入门类路由参数赋值时这张表对应的是答题页最常见的四个状态。需要强调不要把题目列表整个放进State因为两千道题的数组一旦变化Diff更新的成本会上升答题时只保留当前题号和用户作答映射列表交给上一层的“章节练习”去管理。这是刷题APP性能优化的一个简单分层。5. 错题本、知识点标签与刷题记录的进阶实现基础答题流程跑通后源代码项目的“高分感”体现在错题本和进度记录上。许多刷题APP源码做到了“答完题弹个分数”就结束而一个完整的学习闭环应该能让用户随时回看错题。错题本的核心是把答题结果持久化并且保证只在“答错”时写入而不是在选项点击时写入。5.1 错题入库的时机与幂等设计错题本表可以设计成独立表不污染题库表CREATE TABLE IF NOT EXISTS wrong_book ( question_id INTEGER PRIMARY KEY, wrong_count INTEGER DEFAULT 1, last_answer TEXT, updated_at INTEGER );这里用question_id作为主键就可以用insert or replace或者先查再更新的方式来防止一条错题重复插多条。我一般用store.insert前先查一次命中则update如果数据量很大可以直接使用INSERT INTO wrong_book(question_id, wrong_count) VALUES(?, 1) ON CONFLICT(question_id) DO UPDATE SET wrong_count wrong_count 1这条SQL在HarmonyOS的RDB里也能跑前提是把表主键建好。在判分之后用当前题目的id去判断是否与标准答案数组一致不一致再写错题。千万不要在读题页面点一下选项就写库那样用户只是看了一眼还没确定错题本就不干净了。5.2 用AppStorage同步答题进度与全局状态答题总进度、章节通过率需要跨页面共享。HarmonyOS 4提供了AppStorage可以在首页、答题页、错题本之间共享同一个键值。最简单的做法是在EntryAbility的onCreate里先持久化恢复再写入内存AppStorage.setOrCreate(totalAnswered, 0); AppStorage.setOrCreate(wrongCount, 0);页面里读取时StorageProp(totalAnswered) totalAnswered: number 0; StorageLink(wrongCount) wrongCount: number 0;它们的区别在于StorageProp是本页单向同步别的页面修改后当前页自动变但当前页不能反向改StorageLink是双向同步改当前页也会写回AppStorage。如果只是为了“统计答了多少题”用StorageProp即可避免答题页不小心把全局进度清零。需要特别说明的是AppStorage只在应用运行期间存活应用杀进程后会丢失所以要在合适的生命周期把totalAnswered写回Preferences。错题本表已经落库进度可以用getPreferences保存不要用StorageLink和持久化混淆。5.3 真机调试时“当前不会命中断点”的常见原因很多人在DevEco Studio里给Index.ets打一个断点运行后提示“当前不会命中断点”。这个问题在HarmonyOS 4开发中大概率不是代码逻辑问题而是断点打在了编译产物与源码映射不一致的地方。以下几种情况可以逐一排查代码路径里有中文或空格DevEco的sourcemap在某些版本里会错位尽量把工程路径改成纯英文。断点打在Builder方法内部渲染的子组件上构建过程会重新排列代码断点位置可能漂移。开启了release编译运行release包默认不携带调试信息。在真机调试时选择debug变体同时把hvigor的构建模式设为debug。还有一个和源码包相关的问题从zip解压出来的源代码里可能有多个entry目录或残留的.hvigor缓存。导入前把.hvigor、oh_modules、build三个目录删掉再重新同步能解决很多“改代码不生效”和“断点不命中”的问题。因为缓存中的编译产物还是旧的。5.4 从“高分项目”源代码里迁移出自己题库的方法拿到别人的刷题APP源代码最终要改成自己的题库。常见做法是用Python脚本读取Excel或CSVjson.dump成questions.json再替换rawfile里的同名文件。注意编码要UTF-8不能带BOM另外JSON里的每个字段名称要和Question接口完全对应。如果字段对不上导入时大概率不会报错但查询界面上所有题目的内容都会变成undefined这个在初学者源码里非常常见。下面是导入前的一个校验小命令可选python -m json.tool questions.json /dev/null echo json valid如果这一条都过不了说明文件本身有问题不用继续往RDB里导。这样至少在交付前能确认题目资源可被解析。6. 把刷题APP源代码跑起来后的3个验证技巧源代码项目只有在自己手上跑通、能改、能交付才算真正理解。以下几个技巧是我拿到这类刷题APP代码后必做的前置检查。6.1 用索引页的“刷新按钮”验证RDB是否成功导入在Index.ets里加一个临时按钮点击后从question表里count并显示结果。如果显示的不是题目总数说明rawfile导入失败。常见的替代方案是直接查wrong_book空表但空表无法区分“导入失败”还是“确实没有数据”。因此用select count(*) as count from question配合一个日志输出可以在5分钟内定位80%的数据问题。6.2 用hdc命令查日志和拉取数据库文件调试刷题APP时DevEco的log窗口信息太杂可以单独使用hdc命令过滤应用日志hdc shell hilog | grep quiz_app如果怀疑RDB建表失败可以从设备拉库出来检查hdc file recv /data/app/el2/100/base/com.example.quiz/haps/entry/files/databases/quiz.db ./quiz.db拿到本地后用SQLite工具查看表结构和行数比在IDE里面猜快很多。注意受试设备路径可能因版本不同略有差异上面路径是HarmonyOS 4常见的应用私有目录如果找不到可以在设备上执行hdc shell find /data/app -name quiz.db。6.3 验证签名配置最后一步不是“部署发布”而是确认build-profile.json5里的签名配置指向的是自己的证书。很多源代码包附带的是作者本人的发布证书安装到自己手机时会报signature verification failed。去File Project Structure Signing Configs里重新勾选“Automatically generate certificate”然后重新构建。这一步做完才算真正接管了这个刷题APP源代码。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价