結對編程項目
軟件工程 | 這就是鏈接 |
---|---|
作業要求 | 這就是鏈接 |
作業目標 | 熟悉在未結對情況下如何結對開發項目 |
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,個人感覺是各司其職,對方請教時再給建議,要結合實際情況,不過分追求效果。