这就是小学生也会用的四则计算练习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次个人作业 我在这个课程的目标是 学习如何以团队的形式开发软件,提升个人软件开发能力 这个作业在哪 ...
随机推荐
- Unity 打AssetBundle和加载方案
一.如何组织assetBundle: unity5以前,打包需要自己去找依赖,然后需要按照拓扑图顺序压入AB栈,这样在最后打AB时才能有效利用依赖(栈内已有的AB才能作为依赖). unity5.x后, ...
- three.js尝试(一)模拟演唱会效果
工作闲暇之余,偶然翻到了Three.js的官网,立刻被它酷炫的案例给惊艳到了,当即下定决心要试验摸索一番,于是看demo,尝试,踩坑,解决问题,终于搞定了,一个模拟演唱会场景. 主角围绕一个钢管在舞动 ...
- jQuery的那些事儿
jQuery概述 j-JavaScript+Query就是查询js的库,把js中的DOM操作做了封装,实现快速查询使用其中的功能. 优化了DOM操作.事件处理.动画设计和Ajax交互 学习jQuery ...
- C#通过Com串口进行Barcode Printer
前言 工作中有遇到Barcode打印的需求,最开始是通过打印机型号找到对应的打印机,再进行操作,但是需要匹配的打印机型号太多,而且不定,所以处理起来太过麻烦. 后面通过找到通过串口找到打印机,直接传输 ...
- Java字符串的常用方法
[转换] //int 10进制----> 转16进制Integer.toHexString(10) // int 10进制----> 转8进制Integer.toOctalString(1 ...
- 为ASP_NET应用程序启用SQL缓存
步骤一: sql数据库必须开启ServiceBroker服务,首先检测是否已经启用ServiceBroker,检测方法: SELECT DATABASEPROPERTYEX('dbName','IsB ...
- oracle之SQL的基本函数
SQL的基本函数 2.1 单行函数与多行函数 单行函数:指一行数据输入,返回一个值的函数.所以查询一个表时,对选择的每一行数据都返回一个结果. SQL>select empno,lower(en ...
- [剑指Offer]17-打印从1到最大的n位数(递归)
题目 如题,输入n,则从1打印至99. 题解 考虑到n比较大会有大数问题,所以使用字符数组存储数. 由题可用递归求n位全排列,即为所得. 具体地,用临时字符数组用来存答案,每次递归填好一位,都填好后输 ...
- 不定方程(Exgcd)
#include<cstdio> using namespace std; int x,y; inline int abs(int a){return a>?a:-a;} int e ...
- SpringCloud实战 | 第三篇:SpringCloud整合Nacos实现配置中心
前言 随着eureka的停止更新,如果同时实现注册中心和配置中心需要SpringCloud Eureka和SpringCloud Config两个组件;配置修改刷新时需要SpringCloud Bus ...



