
简介一款面向学习者的Android刷题应用源码工程专为Java与Android初学者设计通过“在线做题”场景帮助理解移动端界面搭建、数据存储与请求处理流程。压缩包共845个文件以XML布局资源、PNG图标素材、JSON配置数据、Java源码及编译中间产物为主整体19.57MB便于快速下载并导入Android Studio阅读调试。项目仍处于迭代阶段代码中可看到Activity与Intent跳转、RecyclerView列表绑定、SharedPreferences与SQLite数据存储、网络请求、权限管理、版本兼容等典型知识点配合调试用APK、数据库文件及构建脚本能较完整地展示一个Android工程从资源定义到业务逻辑的组织方式。已有5975人学习或下载对希望借助实际项目积累移动端开发经验、理解刷题类应用功能模块划分的初学者而言是一份信息量密度较高、适合对照学习的参考样例。1. 项目定位与整体设计思路1.1 为什么非要做一个简单的刷题app说实话市面上的刷题软件一抓一大把功能五花八门但真正用起来让人舒服的没几个。我曾经为了备考一个职业资格认证先后下了四五个刷题应用结果要么是开屏弹广告要么是题库跟我的考纲对不上要么是免费版连错题本都要开会员折腾到最后干脆自己动手写了一个。这个项目的定位就是简单两个字不需要登录、不推送广告、不用联网同步打开就能刷题刷完直接关。题库完全由自己维护想塞什么内容就塞什么内容。对我个人来说这解决了临时有个考试要突击、但找不到趁手工具的痛点对想练手的技术学习者来说把一个能跑、能用的完整app从零做出来比对着教程敲一百个demo都有成就感。这个简单刷题app适合谁来参考一是像我一样被考试逼得没办法、想快速搞一个自用工具的开发者二是刚学完前端框架、想找个完整项目练手的同学。它不涉及复杂的后端服务核心就三件事题库怎么存、答题状态怎么管、做题结果怎么统计。把这三点理清楚整个app的骨架就立住了。1.2 技术选型为什么不是原生也不是Flutter选技术栈的时候我对比过三条路线原生AndroidKotlin、Flutter和uni-app。原生开发性能最好但意味着iOS端要再用Swift写一遍维护成本翻倍Flutter的Dart语言需要额外学习成本而且我自己更熟悉Vue生态学起来不划算最后选了uni-app理由很直接一套Vue代码可以同时编译到Android、iOS和H5平时我用H5端调试真机预览也很快没有额外买Mac、配Xcode的麻烦。当然uni-app也不是没有缺点。复杂动画和底层性能跟原生没法比但对于刷题这种以列表、按钮、文本为主的应用场景性能完全够用。实际开发下来冷启动基本在两秒以内滑动列表也没有明显掉帧。如果你手头已经有Android开发环境用原生写当然也行但如果你跟我一样只想用一套代码覆盖多个平台uni-app是目前性价比最高的选择没有之一。1.3 MVP功能范围界定动手之前我先把功能范围划了一条清晰的红线避免后面无休止地加需求。第一版只需要五个模块题库管理按分类加载本地JSON题库支持选择题、判断题、多选题。练习模式顺序答题答完一题立刻显示对错和答案解析。模拟考试模式限时答题全部做完统一出分不留即时反馈。错题本自动收集答错的题目支持重新练习和移除。统计面板展示累计做题数、正确率、分类正确率。至于账号体系、云端同步、排行榜、每日打卡提醒这些第一版一律不做。很多项目半路夭折就是因为需求膨胀先把核心路径跑通后面再加不迟。事实证明这个减法做得对整个开发周期从方案到上线只用了一个星期左右。2. 核心数据结构与关键业务逻辑2.1 题库JSON结构设计题库是整个app的心脏数据结构设计得合理与否直接决定了后面所有功能的开发难度。我采用的JSON结构如下{ category: 计算机基础, questions: [ { id: CS001, type: single, stem: 在TCP/IP协议栈中HTTP协议工作在哪一层, options: [网络接口层, 网络层, 传输层, 应用层], answer: 3, analysis: HTTP是应用层协议它利用TCP传输数据。 }, { id: CS002, type: judge, stem: UDP协议是面向连接的传输协议。, options: [正确, 错误], answer: 1, analysis: UDP是无连接的TCP才是面向连接的。 }, { id: CS003, type: multi, stem: 以下哪些属于关系型数据库, options: [MySQL, Redis, PostgreSQL, MongoDB], answer: [0, 2], analysis: Redis和MongoDB属于NoSQL数据库。 } ] }type字段区分三种题型answer字段在单选和判断题里是数字索引多选题里是数组。这里有个小设计经验无论选型怎么变题干、选项、答案、解析这四个字段一定要保持独立后续做乱序、分组、导出导入都会非常方便。题库文件放在项目的static目录下运行时通过uni.request或直接import加载。考虑到JSON文件可能越来越大我按分类拆成了多个文件比如computer.json、english.json在首页做成题库分类卡片点进去再加载对应的题目数据这样首屏加载压力也小。2.2 答题状态管理方案答题过程中最怕的就是页面一刷新做了一半的题全没了。我一开始用页面局部变量存答题记录结果切后台回来数据就丢了后来老老实实用Vuex统一管理状态。核心状态包括当前题库、当前题目索引、用户答案列表、答题用时和考试模式标志。Vuex里的核心store结构大致是这样export default new Vuex.Store({ state: { currentBank: null, questions: [], currentIndex: 0, answers: {}, mode: practice, // practice | exam timeSpent: 0 }, mutations: { setBank(state, payload) { ... }, setAnswer(state, { qid, value }) { state.answers[qid] value }, nextQuestion(state) { state.currentIndex } } })所有对答题状态的修改都走mutation保证数据流是单向的。实践中一个很关键的细节是单选题存数字索引、判断题存0或1、多选题存数组这样在比较答案时只写一套通用的判断逻辑就行不用为每种题型单独写比较函数。2.3 错题收集与本地持久化错题的判定逻辑看似简单实际上有坑。练习模式里用户选了选项、点了确认之后立刻就能判断对错但模拟考试模式是全部做完才交卷所以错题的筛选必须在交卷那一刻统一执行。我写了一个通用的判题函数接收题目对象和用户答案返回布尔值function checkAnswer(question, userAnswer) { if (question.type multi) { if (!Array.isArray(userAnswer)) return false if (userAnswer.length ! question.answer.length) return false return [...userAnswer].sort().join(,) [...question.answer].sort().join(,) } return question.answer userAnswer }多选题这里有个特别容易踩的坑用户选择的顺序可能跟正确答案的顺序不同比如正确答案是[0,2]用户选了[2,0]如果直接比较数组会判错。所以先排序再join比较虽然多写了两行代码但避免了很隐蔽的逻辑bug。错题数据我存在uni.setStorageSync里key是wrong_booksvalue是一个对象键为题目id值为完整题目信息。重新练习错题时直接从这个存储对象里取题目数组就行练对了再根据id移除。这里要注意的是已存储的错题如果在新版本题库里做了修改旧的缓存就会变成死数据所以每次加载题库时我会比对一下题目版本号不一致就清掉缓存避免展示过期的题目内容。3. 实操过程从零搭建一个可用的刷题app3.1 项目初始化和目录结构规划我用HBuilderX直接创建了uni-app默认模板然后按功能把页面分成四个首页题库选择、答题页核心逻辑、结果页、错题本页。目录结构如下src ├── pages │ ├── index │ │ └── index.vue // 题库列表 │ ├── quiz │ │ └── quiz.vue // 答题页练习/考试共用 │ ├── result │ │ └── result.vue // 成绩与解析 │ └── wrong │ └── wrong.vue // 错题本 ├── static │ └── data │ ├── computer.json // 题库文件 │ └── english.json ├── store │ └── index.js // Vuex状态管理 └── utils └── quiz.js // 判题工具函数这个结构不复杂但每个文件的职责非常清楚。页面之间通过uni.navigateTo跳转参数用URL query传递比如从首页跳到答题页时带上题库文件的路径和模式参数。实测下来这种简单的传参方式在页面刷新时不会丢失而且调试起来很好定位问题。对于题库文件比较大的情况注意别在首页一次性加载所有JSON。我采用的做法是点击分类卡片后在答题页的onLoad生命周期里按需加载对应的JSON文件加载完成后再渲染题目这样首页永远秒开。3.2 答题页的核心实现答题页是整个项目最复杂的部分承载了练习和模拟考试两种模式页面结构大体分为顶部进度区、题干区、选项区和底部操作按钮区。顶部进度条我用了一个简单的进度计算(currentIndex / totalQuestions) * 100用CSS的width绑定这样能直观看到自己做到哪了。题干区用scroll-view包裹保证长题干可以滚动阅读。选项区用v-for渲染每个选项绑定点击事件点击后根据模式决定是否高亮显示。练习模式里用户点击选项就记录答案并高亮同时立即显示确认按钮点击确认后判题并展示正确或错误的样式反馈和解析。模拟考试模式则允许用户随时切换答案底部有一个交卷按钮确认交卷后统一判题然后跳转结果页。template view classquiz-page view classprogress-bar view classprogress-inner :style{ width: progressWidth }/view /view view classquestion-stem{{ currentQuestion.stem }}/view view classoptions-list view v-for(opt, index) in currentQuestion.options :keyindex classoption-item :classoptionClass(index) clickonSelectOption(index) text classoption-label{{ letterMap[index] }}/text text classoption-text{{ opt }}/text /view /view view classfooter button v-ifmode practice clickonConfirm确认/button button v-else clickonSubmitExam交卷/button /view /view /template这里有一个我自己写项目时总结的经验点击选项之后选项的高亮样式一定要通过计算属性实时绑定类名而不是直接改DOM样式。uni-app在H5端直接操作DOM可能没问题但编译到小程序端就不一定了老老实实用数据驱动才最保险。3.3 计时器与交卷逻辑模拟考试模式需要限时功能我用setInterval实现一个简单的计时器每秒钟把Vuex里的timeSpent加1然后在页面显示成MM:SS格式。这里有个细节计时器的启动和清除一定要在页面的onLoad和onUnload生命周期里做否则页面返回时计时器还在跑内存泄漏不说数据还会错乱。交卷逻辑里还有一个容易忽略的点用户可能还有题目没作答。交卷时我需要遍历所有题目把没答过的题目标记成未作答统计时单独归类。最终传到结果页的数据结构包含总题数、已答数、正确数、错题列表结果页只需要负责展示。关于跳转结果页的方式我一开始用uni.navigateTo后来发现从结果页返回答题页会保留旧状态体验很怪。改成uni.redirectTo之后逻辑清爽多了交卷后直接替换当前页面结果页返回时回到首页符合操作直觉。3.4 本地存储与数据读写刷题app的本地存储需求集中在三块答题进度、错题本、统计数据。我的方案是全部用uni.setStorageSync和uni.getStorageSynckey分别叫quiz_progress、wrong_books和statistics。每次切换题目时把当前索引和答案列表写入storage每次交卷或确认一题后把错题和统计数据更新到storage。写存储的时候要注意setStorageSync是同步操作频繁调用会造成页面卡顿。我的策略是关键节点写入比如练习模式里只有点确认之后才更新错题本而不是每次点击选项都写。考试模式的进度每切换一题存一次频率也不高实测没有任何性能问题。为了统计正确率每次判题完成后把结果追加到statistics里。统计维度包括总做题数、正确数、错误数和各分类的正确率。因为数据结构很简单前端直接用数组reduce就能算出结果不需要额外引入图表库用简单的进度条展示就够了。4. 常见问题与避坑指南4.1 真机调试和打包环节的几个坑开发过程中调试基本都在H5端逻辑跑通之后拿到手机上真机预览结果发现问题和H5端表现完全不一样。第一个坑是字体大小。H5端看着刚刚好的字体真机上会偏小尤其在option文本上特别明显。这个没有统一解法只能真机实测调整建议直接把文字默认大小设大一到两号适配成本最低。第二个坑是底部安全区。iPhone X以后的机型底部有home indicator区域如果页面底部按钮没做适配会被手势条挡住。uni-app提供了env(safe-area-inset-bottom)的方案在按钮容器上加上对应的padding就可以解决。第三个坑是离线打包。HBuilderX的云打包虽然省事但正式发布时如果接入了原生插件必须走离线打包流程否则插件无法生效。如果只是自用云打包就够了但要是上架应用商店建议提前研究离线打包的签名和证书配置问题。4.2 题库文件大时的加载策略我最初把全部题库塞进一个JSON文件结果随着题目不断增多加载时间肉眼可见地变长。后来改成按分类拆分文件后单文件大小控制在200KB以内再配合uni.showLoading提示加载中体验好了很多。如果题目量特别大比如上万道题前端加载全部题库会拖垮内存。这种情况下正确的做法是引入数据库如sqlite或者按章节分批加载。刷题应用通常不会一次性用到全部题目一次加载一个分类就够了。我实测600道题的JSON约300KB解析时间不到百毫秒完全能接受。另外JSON文件里不要存冗余字段。比如选项字母这种完全可以通过索引推导的内容就没必要存减小文件体积的同时也减少了出错的可能性。我的字段最少化原则能靠代码生成的字段一律不写进数据文件。4.3 用户交互体验上的经验沉淀答题页有一个体验细节值得单独说切换题目时页面顶部滚动位置应该保持住。如果题干很长用户滚动到下方看了选项点击下一题后新题目出现时页面应该回到顶部否则会停留在上一题的阅读位置非常影响体验。我在watch currentIndex变化时手动调用uni.pageScrollTo({ scrollTop: 0 })解决。还有一个交互选择是练习模式是否显示即时反馈。我最终选择了点击选项→确认→显示对错和解析三步流程而不是点击选项立刻判对错。原因是立刻判对错会让用户有蒙答案的倾向看到选对了就不看解析了加了确认步骤后用户至少会花几秒钟把题目和解析看完记忆效果更好。这种细节对刷题效果影响很大值得认真考虑。统计数据页我做了两个维度的展示累计统计和单次统计。累计统计就是所有做题记录的正确率曲线单次统计是一次练习或考试的得分和用时。这两个维度缺一不可光看总正确率很难发现自己的薄弱章节加上分类正确率之后复习方向就清晰多了。4.4 后续扩展方向第一版做完之后我已经在琢磨几个后续优化方向。题目支持图片是一个刚需尤其医学、地理类题目纯文字表达不清楚题干。数据结构里加一个image字段答题页把图片渲染在题干下方改动成本不大但价值很高。还可以增加一个每日一练功能从题库里随机抽20道题生成当天计划。这个功能不需要额外服务端只要在本地做随机数过滤就行。再有就是导入导出的能力目前题库是直接放在代码里的普通用户没法自己添加题目做一个JSON文件的导入入口或者再加一个简单的在应用内编辑题目的界面这样整个工具就能覆盖更完整的闭环。从我的使用习惯来看这个app做到现在这个程度已经满足日常需求了后续功能都是锦上添花不会为了堆功能去牺牲简单这个核心定位。本文还有配套的精品资源点击获取