ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Android考试管理系统实战:Room建表、倒计时与自动判分

Android考试管理系统实战:Room建表、倒计时与自动判分 简介这是一份面向移动应用开发课程学习者的Android考试管理系统课程设计报告适用于高校软件工程、计算机相关专业的课程作业与毕业设计参考。报告围绕线上教学环境下的考试管理难题展开完整收录项目背景、开发技术、详细设计、运行演示与心得体会等章节并配有大量界面截图与架构示意图正文约14805字。压缩包内共1个doc文件约3.31MB可直接查阅排版完整的论文正文。内容覆盖Java语言基础、Android Studio开发环境、MVC三层架构与SQLite数据库等关键知识点并给出DonePaperModel、QuestionsModel、UserModel等实体类划分以及添加试卷、选择试卷、查询分数、管理试卷等界面的Layout与控制层Activity设计说明。目前已有322人学习适合需要模仿论文结构、借鉴系统模块划分、准备答辩或对照实现考试管理类App的读者参考使用。1. 考场发卷慢、判分乱Android 考试管理系统要接住什么每学期期末机房老师最头疼两件事把卷子发下去再收上来以及收完之后按答案一道道对分。一个班四十人、每人三十道客观题手工判分加登分至少两小时中间还容易把 A 的分数填到 B 的格子里。Android 考试管理系统就是把这套流程搬进平板和手机学生端登录后拉取试卷、倒计时作答、提交后立刻出分教师端维护题库、组卷、看成绩分布。它适合课程设计、企业内训考核、题型固定的资格模拟考这类并发不高、题目结构规整的场景。也正是因为它对服务端依赖轻、离线可跑、上手门槛低成了 Android 课程设计里被反复选中的题目。下面这套方案只围绕一个目标环境能配起来、数据库能立住、判分不出错、真机跑得通。2. 搭开发环境Android Studio、SDK 与 Gradle 一次配齐环境是劝退率最高的一环。很多同学代码还没写一行先卡在 Gradle 同步失败、SDK 找不到、真机连不上。这一章按安装、配置、连设备三步走每一步都给可复制的命令和配置。2.1 Android Studio 与 Android SDK 的版本搭配装 Android Studio 之前先想清楚 SDK 要装哪几个。Android Studio 本身只是 IDE真正决定编译能不能过的是 SDK Platform 和 Build-Tools。打开 SDK Manager至少勾三项一个目标 API 的 Platform、对应版本的 Build-Tools、以及 Platform-Tools这里面有 adb。目标 API 选你手机系统版本能覆盖到的那个即可不必盲目追最高新 API 往往要求更高的 Gradle 和 AGP 版本反而增加同步失败的概率。如果你习惯中文界面Android Studio 的设置里有语言选项装官方中文语言包后重启即可切换但要注意部分插件在中文界面下菜单名会变跟着英文教程操作时容易对不上建议配置阶段先用英文界面功能跑通再换。SDK 路径建议不要放在带空格或中文的目录下比如放在D:\Android\Sdk。路径里一旦出现空格某些老版本构建工具解析会出错报错信息还很难读懂。2.2 Gradle 与 JDK 配置同步失败的三个高发点Android Studio 自带 JDK但 Gradle 版本和 AGP 版本必须互相匹配否则一同步就红。判断标准是看根目录gradle/wrapper/gradle-wrapper.properties里的 distributionUrl 和build.gradle里的 AGP 版本是否在官方兼容表内。改版本时两个文件要一起动不要只改一个。网络是第二个高发点。默认仓库走的是海外源同步时经常卡在下载依赖。把仓库换成公开镜像可以显著提速这是行业里非常常见的做法// settings.gradle dependencyResolutionManagement { repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) repositories { // 公开镜像源按顺序命中减少跨境拉取 maven { url https://maven.aliyun.com/repository/public } maven { url https://maven.aliyun.com/repository/google } google() mavenCentral() } }逻辑说明repositoriesMode设为FAIL_ON_PROJECT_REPOS表示禁止各模块自己声明仓库统一在 settings 里管避免依赖来源混乱。镜像放在最前面命中不到再回落到 google 和 mavenCentral。参数说明如果只用镜像仍有个别包拉不下来不要急着删镜像先看报错里的坐标那多半是某个仓库特有的 artifact保留google()兜底即可。第三个高发点是内存gradle.properties里把org.gradle.jvmargs调到-Xmx2048m以上大项目同步时不容易 OOM。2.3 真机调试adb 连接与常见识别问题模拟器跑考试系统体验很差倒计时、触摸手感和真机差距大建议直接用真机。手机打开开发者选项里的 USB 调试插线后执行# 查看设备是否被识别 adb devices # 若显示 unauthorized在手机上确认授权弹窗 adb kill-server adb start-server # 安装调试包路径按你实际输出改 adb install -r app/build/outputs/apk/debug/app-debug.apk # 实时看应用日志过滤自己包的 TAG adb logcat -s ExamApp:V AndroidRuntime:E逻辑说明adb devices返回的序列号后面跟device才表示可用显示unauthorized是手机没点授权显示offline通常是数据线或驱动问题。adb logcat -s里的-s是静默模式只输出指定 TAGAndroidRuntime:E用来抓崩溃堆栈。参数说明把ExamApp换成你自己在代码里Log.d(ExamApp, ...)用的 TAG。无序刷新日志可以不加-s但日志量大时找崩溃会非常痛苦。如果公司电脑装了手机助手类软件它可能抢占 adb 端口关掉它再重试。3. 详细设计Room 建表、实体关系与 DAO详细设计的核心不是画多漂亮的图而是把学生答一道题系统怎么记住、怎么算分、怎么防止重复提交这件事拆成可落地的表和接口。Android 端本地持久化Room 是最省心的选择它在 SQLite 上加了一层编译期校验写错字段名编译就报错比裸写 SQLiteOpenHelper 少踩很多坑。3.1 从需求抽实体四张表撑起整套系统先把名词圈出来学生、试卷、题目、答题记录。教师端还要组卷所以再加一张试卷题目关联表。整理成下面这张表字段就不用边写边想。表名主键关键字段说明studentidname、no、pwd学号唯一pwd 存加盐哈希paperidtitle、duration、totalScoreduration 单位秒控制倒计时questionidpaperId、stem、options、correct、scorepaperId 外键一张卷多道题answer_recordidstudentId、paperId、questionId、chosen、submitTime一次作答一行便于审计外键在 Room 里不是强制开启的需要显式声明。答题记录表不建议做整卷一行存 JSON那样后期想查某道题的正确率就得把 JSON 全解析一遍统计成本高。拆成一行一题SQL 聚合直接出结果。3.2 Room 实体类与建表实体类就是表结构的 Kotlin 映射字段类型、默认值、是否可空都在这里定死Entity(tableName question) data class Question( PrimaryKey(autoGenerate true) val id: Long 0, val paperId: Long, val stem: String, // 题干 val optionA: String, val optionB: String, val optionC: String, val optionD: String, val correct: String, // A/B/C/D val score: Int 2 // 本题分值 ) Entity( tableName answer_record, foreignKeys [ForeignKey( entity Question::class, parentColumns [id], childColumns [questionId], onDelete ForeignKey.CASCADE // 题被删则记录一并清掉 )], indices [Index(questionId)] ) data class AnswerRecord( PrimaryKey(autoGenerate true) val id: Long 0, val studentId: Long, val paperId: Long, val questionId: Long, val chosen: String?, val submitTime: Long System.currentTimeMillis() )逻辑说明PrimaryKey(autoGenerate true)让主键自增id给默认值 0 是为了插入时不必手动指定。ForeignKey上挂onDelete CASCADE删题时对应作答记录自动清理避免出现指向不存在题目的脏数据。参数说明indices [Index(questionId)]很关键Room 在编译时会对未加索引的外键列给出警告而且统计每题正确率这类查询走全表扫描会明显变慢。chosen设为可空用来表示这道题学生没作答和作答了但答错区分开。3.3 DAO 与事务提交答卷怎么保证一致性DAO 是数据访问接口提交答卷这种多表写入必须包在事务里否则中途失败会留下半份答卷Dao interface ExamDao { Query(SELECT * FROM question WHERE paperId :paperId ORDER BY id) suspend fun loadQuestions(paperId: Long): ListQuestion Insert(onConflict OnConflictStrategy.ABORT) suspend fun insertAnswers(records: ListAnswerRecord) // 统计每题正确率用于教师端分析 Query( SELECT questionId, SUM(CASE WHEN chosen (SELECT correct FROM question q WHERE q.id questionId) THEN 1 ELSE 0 END) * 1.0 / COUNT(*) AS rate FROM answer_record WHERE paperId :paperId GROUP BY questionId ) suspend fun correctRate(paperId: Long): ListRateRow Transaction suspend fun submit(records: ListAnswerRecord) { insertAnswers(records) // 批量插入任一失败整体回滚 } }逻辑说明Transaction标注的默认方法里调用insertAnswersRoom 会开启一个数据库事务批量插入要么全成功要么全回滚防止学生提交到一半进程被杀导致答卷不完整。参数说明OnConflictStrategy.ABORT表示主键冲突直接抛异常提交场景下这比REPLACE更安全因为REPLACE会先删后插可能悄悄丢掉旧记录。correctRate里用子查询关联 correct 字段* 1.0是为了让整数除法变成浮点除法否则正确率算出来永远是 0 或 1。4. 核心功能实现倒计时、进度条、自动判分界面跑起来之后真正的技术点在几处容易翻车的地方倒计时会不会因为界面销毁而乱跳进度条会不会卡顿判分逻辑会不会把未作答当成错答。这一章逐个拆。4.1 倒计时与 Android 进度条怎么做才不闪倒计时不要用Thread.sleep也不要用CountDownTimer直接更新 UI 后不复用。用主线程 Handler 每秒 tick 一次配合ProgressBar显示剩余进度private val handler Handler(Looper.getMainLooper()) private var remainMs 60 * 60 * 1000L // 总时长从 paper.duration 读 private val totalMs remainMs private val ticker object : Runnable { override fun run() { remainMs - 1000 // 进度条按毫秒换算避免每秒跳一格显得顿 binding.pbExam.max 1000 binding.pbExam.progress (remainMs * 1000 / totalMs).toInt() binding.tvCountdown.text formatHms(remainMs) if (remainMs 0) { submitPaper(auto true) // 时间到自动交卷 } else { handler.postDelayed(this, 1000) } } } override fun onStart() { super.onStart(); handler.post(ticker) } override fun onStop() { handler.removeCallbacks(ticker) // 防止界面不可见时仍在跑 super.onStop() }逻辑说明onStart启动、onStop移除回调这样切后台时计时不空转回到前台后从剩余时间继续。pbExam.max设为 1000 是为了拿到千分比精度进度条每秒推进一次也不会显得突兀。参数说明remainMs的初值应从paper.duration读出而不是写死方便教师端改考试时长。真正上线的考试系统还要把剩余时间落到本地进程被杀后重新进入能续上否则学生接个电话就可能被判定超时。4.2 自动判分与得分统计判分本身不复杂难的是边界未作答、多选、题目分值不同都要覆盖。用 Map 存学生选择遍历题目累加fun grade(questions: ListQuestion, chosen: MapLong, String?): GradeResult { var score 0 var right 0 questions.forEach { q - val pick chosen[q.id] if (pick ! null pick q.correct) { // null 表示未作答直接不算分 score q.score right } } return GradeResult(score, right, questions.size) }逻辑说明pick ! null这一句必须写否则某些写法下null A会因为类型推断出意外结果。GradeResult里同时带出正确题数和总题数教师端展示成绩分布时不用再查一次库。参数说明如果题干是判断题correct直接存 T/F判分逻辑不用改只要保证选项和答案的字符串规范一致。多个模块共用这套判分建议把它抽到一个无 Android 依赖的纯 Kotlin 类里这样能在本地单元测试里直接跑改判分规则时不用每次都装到真机上验证。4.3 断线续考本地草稿缓存考试系统最怕的是中途丢失作答。做法是每选一题就把当前进度写进本地用轻量存储即可// 每次选择后调用key 按 学生试卷 隔离 fun cacheDraft(ctx: Context, key: String, chosen: MapLong, String?) { val json JSONObject().apply { chosen.forEach { (qid, ans) - put(qid.toString(), ans ?: ) } } ctx.getSharedPreferences(exam_draft, Context.MODE_PRIVATE) .edit().putString(key, json.toString()).apply() }逻辑说明key用studentId_paperId拼避免不同学生或不同试卷的草稿互相覆盖。用apply()异步落盘不阻塞 UI 线程。参数说明不需要加commit()apply()对考试草稿这种场景足够。恢复时先读草稿再渲染选项注意把null还原成未选中状态。草稿在正式提交成功后要清掉否则下次进同一场考试会看到上次的旧答案。5. 运行演示与排错从 Logcat 到真机验证功能写完只是第一步真机上跑一遍往往还会冒出几个只在特定机型出现的问题。这一章给一套验证顺序和排错方法比盲改代码高效得多。5.1 一次完整考试流程的验证清单演示前按顺序走一遍每步都确认结果别等录屏时才发现问题步骤操作预期结果失败先看哪1学号登录进入试卷列表SharedPreferences 是否写进去2开始考试倒计时启动、进度条推进onStart 是否被调用3选若干题后杀进程重进能恢复草稿draft key 是否匹配4提交立即出分并写库Logcat 里 insert 是否有异常5教师端看统计正确率按题展示聚合 SQL 的 GROUP BY这个清单的价值在于它把看起来能跑推进到流程闭环考试系统的演示分往往就丢在草稿恢复和统计查询这两个容易被跳过的环节上。5.2 常见崩溃与 Logcat 定位技巧崩溃九成集中在主线程做耗时操作和空指针两处。判分、写库这些操作要放到Dispatchers.IO的协程里别在点击事件里直接跑大批量插入。空指针高发在从数据库取到记录后直接.chosen.length这种写法chosen是可空字段必须先判空。# 只抓崩溃堆栈遇到 System.exit 或 FATAL 立刻停下来看 adb logcat -b crash # 抓当前应用全部输出并写文件方便反复比对 adb logcat -d exam_log.txt逻辑说明-b crash只读崩溃缓冲区比在几千行日志里翻找快得多-d表示导出当前缓冲后退出不阻塞终端适合演示前先抓一份基线日志。参数说明崩溃日志里第一行Caused by通常才是根因FATAL EXCEPTION那行只是入口。如果是SQLiteConstraintException多半是外键约束或主键冲突回到表结构和 OnConflictStrategy 上排查。真机上偶发的白屏可以配合布局检查器看层级但更常见的原因是主题里带了启动图却没引用资源这种在低端机型上尤为明显。把计时器抽成独立类、判分逻辑做成可单测的纯函数、草稿缓存按学生隔离这三件事做完整套考试管理系统在真机上的稳定性会有明显提升演示时也更从容。本文还有配套的精品资源点击获取
返回列表