这就是小学生也会用的四则计算练习APP吗?- by软工结对编程项目作业
结对编程项目
| 软件工程 | 这就是链接 |
|---|---|
| 作业要求 | 这就是链接 |
| 作业目标 | 熟悉在未结对情况下如何结对开发项目 |
Github与合作者
合作者(学号):
- 区德明:318005422
- 虚左以待
Github链接:
https://github.com/DMingOu/CalculateExercise
由于本来的合作者临时有事不能和我一起结对编程,所以我也只好咬咬牙单刀赴会了。
因为本次的题目要求有图形化界面,而我也学过移动开发的,所以一不做二不休,直接提刀杀进AndroidStudio,开始秃头设计一款小学生也能用的四则计算题练习工具App,一来可以简化方便操作和展示,二来也算是在众多.exe中里面可以独树一帜。
一、PSP
| PSP2.1 | Personal Software Process Stages | 预估耗时 (分钟) | 实际耗时 (分钟) |
|---|---|---|---|
| Planning | 计划 | 25 | 38 |
| · Estimate | · 估计这个任务需要多少时间 | 25 | 38 |
| Development | 开发 | 945 | 830 |
| · Analysis | · 需求分析 (包括学习新技术) | 75 | 130 |
| · Design Spec | · 生成设计文档 | 100 | 80 |
| · Design Review | · 设计复审 | 60 | 35 |
| · Coding Standard | · 代码规范 (为目前的开发制定合适的规范) | 30 | 60 |
| · Design | · 具体设计 | 120 | 110 |
| · Coding | · 具体编码 | 260 | 260 |
| · Code Review | · 代码复审 | 60 | 45 |
| · Test | · 测试 (自我测试,修改代码,提交修改) | 95 | 110 |
| Reporting | 报告 | 120 | 105 |
| · Test Repor | · 测试报告 | 60 | 50 |
| · Size Measurement | · 计算工作量 | 30 | 30 |
| · Postmortem & Process Improvement Plan | · 事后总结, 并提出过程改进计划 | 30 | 25 |
| · 合计 | 1090 | 973 |
二、四则运算练习工具APP需求分析
| 需求描述 | 是否实现 |
|---|---|
| 控制生成题目的个数 | 是 |
| 控制题目中数值范围 | 是 |
| 计算过程不能产生负数,除法的结果必须是真分数,题目不能重复,运算符不能超过3个 | 是 |
| 显示题目 | 是 |
| 显示答案 | 是 |
| 能支持一万道题目的生成 | 是 |
| 判定答案中的对错并进行数量统计 | 是 |
| 显示得分结果 | 是 |
| 具有图形化的操作界面 | 是 |
三、APP开发计划
| 功能 | 描述 | 开发者 | 进度 |
|---|---|---|---|
| 生成题目 | 随机生成操作数和运算符,组成有效的四则运算表达式 | 区德明 | 完成 |
| 计算结果 | 根据生成的表达式,计算生成正确的结果 | 区德明 | 完成 |
| 练习报告 | 输出成绩结果 | 区德明 | 完成 |
| UI界面设计 | 设计软件的界面设计XML | 区德明 | 完成 |
| UI界面实现 | 使用Kotlin语言实现 Andoid APP | 区德明 | 完成 |
| 功能测试与故障修复 | 测试程序的功能,修复出现的故障 | 区德明 | 完成 |
| 性能分析与优化 | 分析程序执行的性能,优化性能表现 | 区德明 | 完成 |
结构图


四、方案
4.1 生成题目的算法设计思路
线程池多线程并发,创建指定数量的题目,并利用Set集合进行去重操作。
核心代码如下:
执行生成题目的线程任务类
/**
* 执行生成指定数量题目任务
* 线程类
*/
private class GenerateWorker(
private val exerciseNum: Int, //生成数量
private val numRange: Int, //范围上限
private val workerIndex: Int, //工作线程的编号
private val countDownLatch: CountDownLatch,
private val tempList : MutableList<Exercise>
) : Runnable {
override fun run() {
try {
val start = System.currentTimeMillis()
val exerciseList = generateExercises(
exerciseNum, numRange
)
//将 去重但是未编号的 列表 加入缓冲列表中
tempList.addAll(exerciseList)
countDownLatch.countDown()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
执行完善收尾操作的线程任务:
/**
* 用于补充题目序号, 完善,的任务
*/
private class SupplyWorker(
private val exerciseNum: Int,
private val countDownLatch: CountDownLatch,
private val tempList : MutableList<Exercise> ,
private var startIndex : Int ,
private val mHandler: QuestionListHandler //回调UI线程
) : Runnable {
override fun run() {
getExerciseOnCount(exerciseNum)
countDownLatch.countDown()
//根据已有的列表数据,进行编号
for(exercise in tempList) {
startIndex++
exercise.number = startIndex
}
updateGlobalExerciseList(tempList)
//通知 View层更新内容
val message = Message()
message.what = 666
mHandler.dispatchMessage(message)
}
}
提供给外界调用的入口:
fun produce(exerciseNumber: Int, numberRange: Int,startIndex : Int, handler: QuestionListHandler) {
if (exerciseNumber == 0 || numberRange == 0) {
return
}
//最小范围是2
if (numberRange < 2) {
throw RuntimeException("范围上限不可以少于2")
}
//手工计时器启动
start = System.currentTimeMillis()
//设置本次加载的起始序号
this.startIndex = startIndex
//每个线程的工作量
val workload = 25
//记录线程编号
var index = 1
//剩余工作量
var remainWorkload = exerciseNumber
//启动创建题目的线程组
val threadCount = if (exerciseNumber % workload == 0)
exerciseNumber / workload
else
exerciseNumber / workload + 1
//生成线程+一个输出线程
countDownLatch = CountDownLatch(threadCount + 1)
//任务正式启动前。清空缓存列表,避免脏数据
tempExerciseList.clear()
while (true) {
//执行生成题目的任务线程,编号对应
remainWorkload -= if (remainWorkload > workload) {
execute(
GenerateWorker(
workload,
numberRange,
index++,
countDownLatch,
tempExerciseList
)
)
workload
} else {
execute(
GenerateWorker(
remainWorkload,
numberRange,
index,
countDownLatch,
tempExerciseList
)
)
break
}
}
//启动完善每一道题的任务
execute(SupplyWorker(exerciseNumber, countDownLatch ,tempExerciseList ,startIndex , handler))
//数据创建完毕,重置状态
countDownLatch.await()
clear()
}
生成题目的操作,实际执行:
@JvmStatic
fun generateExercises(exercisesNum: Int, numRange: Int , startIndex : Int = -1): List<Exercise> {
//控制编号
var index = startIndex
//如果没有传入编号则视为生成时不做编号处理
val isSetIndex = index!=-1
//控制生成循环的结束
var count = 0
val exerciseList: MutableList<Exercise> = ArrayList()
while (count < exercisesNum) {
val exercise = generateQuestion(numRange)
generateAnswer(exercise)
//有效题目加入List
if (validate(exercise)) {
count++
if(isSetIndex) {
index++
//设置序号
exercise.number = index
}
//生成可以输出的题目样式
EXERCISE_QUEUE.add(exercise)
exerciseList.add(exercise)
//放入去重Set,用作判断
exercisesSet.add(exercise.simplestFormatQuestion)
}
}
return exerciseList
}
4.2 客户端(用户入口)的设计思路
采用单Activity+多Fragment的架构。
- util包提供算法和数据源的能力
- View包承载页面UI的显示,
- bean包存放实体类信息
- widget包存放的是一些自定义控件

五、效能分析
通过大厂滴滴的开源性能工具平台,DoKit,接入App,进行性能的进一步分析,如下:
5.1 程序效能
使用APP的出题功能,分别生成1000道,2000道,10000道题目不同的消耗内存情况
- 生成1000道题目时,只需要130MB
- 生成2000道题目时,只需要155MB
- 生成10000道题目的时候,就需要1005MB了
可以认为题目的数量影响着App的运行时所消耗的资源及内存,并随着题目的数量成正比例关系。


cpu消耗如下:

页面打开跳转的时长:

5.2 性能优化
首轮优化

可以看出线程池的线程配置对于大数据量非常重要,从生成10000条题目为例子,在4个线程在并发的创建对象的时候,因为设置的单个线程任务量为2000,无法完成剩下的2000任务,只能等4个线程任务都完成后再创建新线程执行任务。
解决方案:调整线程池的配置,提高线程的数量,提高每个生成题目的线程的任务数量
优化后的结果:
线程池复用线程,分页加载50条数据,逐步的加载出全部的数据,分散内存和CPU的密集消耗。
优化后的生成10000条数据,耗时从10秒提高到了7秒,提升了30%。如果继续对线程池的配置进行优化,效率还会继续提高。

进一步优化
但是对于移动应用来说,加载时间的耗时始终是大问题,让用户觉得等待时间过长,同时会有卡顿的问题,亟待解决,所以下面要通过性能分析工具,分析哪个步骤流程才是在生成题目列表的过程中造成卡顿呢??通过DoKit的卡顿堆栈分析图:


可以看出卡顿 与 打开页面,创建对应数据,数据创建后,列表UI开始加载数据, 频繁大量地创建View对象息息相关。
进一步解决方案:分页加载
数据量过大的时候,其实不需要一口气全部加载进去,可以采用分页加载的方式加载与创建对象,吞吐量减少的同时CPU和内存的占用也降低了。从另一个角度讲,页面也无必要一口气加载上千条乃至万条的数据 ( 屏幕就这么大 ,用户看不过来)
设定分页加载题目的方式,每页固定数量为 50 ,若所需数量不足50条,则继续加载所需的数量题目。
打开性能检测平台的监控 CPU,内存 ,帧率
未加载数据前:

打开页面,加载10000条数据:

可以看出,优化后,加载大数据,对页面的影响小了很多,CPU和内存的短时间消耗急剧降低,对App这种只拥有固定内存(少量)的进程,可以有很不错的效果。
六、APP真机测试报告
6.1 测试1:程序生成四则计算题和答案是否符合题目要求
结果说明:
以上为测试生成100道数值10以内的题目的截图
可以得出结论,看到题目是符合去重的要求且答案是正确的
6.2 测试2:程序是否能正确判断用户输入答案的正确性
以 6 道题目为例子,查看练习情况报告,可以看到可以给出正确答案提示,以及完成的情况,错对情况,得分率,很详细。

6.3 测试3:能否支持一万道题目的生成与显示
在出题模式下执行生成10000道数值范围在5以内的题目的功能
最终效果
![]() |
![]() |
|---|---|
| 首页 | 题目列表 |
![]() |
![]() |
| 练习做题 | 练习情况报告 |
七、总结
项目小结
获得的经验:
虽说只是做了一个小工程,从中也学习到了多线程的相关知识,数据结构的使用和APP界面的设计与布局。
不足的地方:
没有做查看历史做题的历史记录。
生成大批量的题目的时候,会随着页面加载越来越多的题目,随着RecyclerView持有的子itemView数量逐渐增多会导致分页加载,RecyclerView时会有卡顿感。
结对感受
期待ing,个人感觉是各司其职,对方请教时再给建议,要结合实际情况,不过分追求效果。
这就是小学生也会用的四则计算练习APP吗?- by软工结对编程项目作业的更多相关文章
- 3.结对编程成果报告(小学生四则运算的出题程序,Java实现)
程序名称:小学生四则运算的出题程序 先附上代码: package com.makequestion; import java.text.DecimalFormat;import java.util.R ...
- 软工作业NO.2小学生线上杨永信——四则运算题目生成
项目题目:实现一个自动生成小学四则运算题目的命令行程序 github地址:https://github.com/a249970271/Formula 驾驶员:梁沛诗 副驾驶:曾祎祺 项目说明 自然数: ...
- 【BUAA软工】结对编程作业
项目 内容 课程:2020春季软件工程课程博客作业(罗杰,任健) 博客园班级链接 作业:BUAA软件工程结对编程项目作业 作业要求 课程目标 学习大规模软件开发的技巧与方法,锻炼开发能力 作业目标 完 ...
- 【Beta阶段】展示博客
Beta阶段展示博客 blog software buaa 1.团队成员简介 Email:qianlxc@126.com Free time:8:00 7:00 a.m ~ 11:00 12:00p. ...
- [2017BUAA软工助教]第0次作业小结
BUAA软工第0次作业小结 零.题目 作业链接: This is a hyperlink 一.评分规则 本次作业满分10分: 按时提交有分 一周内补交得0分 超过一周不交或抄袭倒扣全部分数 评分规则如 ...
- [2017BUAA软工助教]个人项目小结
2017BUAA个人项目小结 一.作业链接 http://www.cnblogs.com/jiel/p/7545780.html 二.评分细则 0.注意事项 按时间完成并提交--正常评分 晚交一周以内 ...
- 【个人项目总结】C#四则运算表达式生成程序
S1&2.个人项目时间估算 PSP表格如下: PSP2.1 Personal Software Process Stages Time(Before) Time(After) Planning ...
- [BUAA软工]第二次博客作业---结对编程
[BUAA软工]结对作业 项目 内容 这个作业属于哪个课程 北航软工 这个作业的要求在哪里 2019年软件工程基础-结对项目作业 我在这个课程的目标是 学习如何以团队的形式开发软件,提升个人软件开发能 ...
- [BUAA软工]第一次博客作业---阅读《构建之法》
[BUAA软工]第一次博客作业 项目 内容 这个作业属于哪个课程 北航软工 这个作业的要求在哪里 第1次个人作业 我在这个课程的目标是 学习如何以团队的形式开发软件,提升个人软件开发能力 这个作业在哪 ...
随机推荐
- [BUUOJ记录] [BJDCTF2020]EasySearch
前面的突破点考察swp泄露以及md5截断认证,最后一步考察ssi注入 进入题目是一个登陆页面什么提示都没有,工具扫了一下发现swp泄露,得到登录验证页面的源码: <?php ob_start() ...
- vant ui TabBar封装
TabBar.vue基本上是放在App.vue里面,都存在 <template> <div id="app"> <home-tab-bar :tar- ...
- Spring security OAuth2.0认证授权学习第四天(SpringBoot集成)
基础的授权其实只有两行代码就不单独写一个篇章了; 这两行就是上一章demo的权限判断; 集成SpringBoot SpringBoot介绍 这个篇章主要是讲SpringSecurity的,Spring ...
- leetcode刷题-55跳跃游戏
题目 给定一个非负整数数组,你最初位于数组的第一个位置. 数组中的每个元素代表你在该位置可以跳跃的最大长度. 判断你是否能够到达最后一个位置. 思路 贪心算法:记录每一个位置能够跳跃到的最远距离,如果 ...
- 怎么把后台传过来的Json拼成table 用Jquery ajax()
页面上的表格定义:<table id="tableId"></table> js中的代码:var $parent0 = $('#tableId);//获取页 ...
- JS基础回顾_Dom
方法 reverse 将子元素位置颠倒 <!-- log --> <ul id="reverse"> <li>1</li> < ...
- 关于在异步操作中访问React事件对象的小问题
最近撸React的代码时踩了个关于事件处理的坑,场景如下:在监听某个元素上会频繁触发的事件时,我们往往会对该事件的回调函数进行防抖的处理:防抖的包装函数大致长这样: debounce = (fn, d ...
- git仓库下拉和上传
git仓库比较方便,可以实现白天在公司写的代码,下班之前上传到git仓库,晚上在另一台电脑上直接下拉下来,其实感觉和开发用的svn差不多 在另一篇博客里面写到,需要先在git里面新增好仓库和成员之后, ...
- 整理的网上的MySQL优化文章总结
MySQL优化 Linux优化 IO优化 调整Linux默认的IO调度算法. IO调度器的总体目标是希望让磁头能够总是往一个方向移动,移动到底了再往反方向走,这恰恰就是现实生活中的电梯模型,所以IO调 ...
- 【系统之音】Android进程的创建及启动简述
Android系统中的进程(这里不包括init等底层的进程)都是通过Zygote fork而来的,那这些进程的启动流程都是怎样的呢? 这里将Android进程分为两个部分: (1)系统框架进程Syst ...



