這就是小學生也會用的四則計算練習APP嗎?- by軟工結對程式設計專案作業

DMingO發表於2020-10-11

結對程式設計專案

軟體工程 這就是連結
作業要求 這就是連結
作業目標 熟悉在未結對情況下如何結對開發專案

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 區德明 完成
功能測試與故障修復 測試程式的功能,修復出現的故障 區德明 完成
效能分析與優化 分析程式執行的效能,優化效能表現 區德明 完成

結構圖

image-20201010222426647

image-20201010222450625

四、方案

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包存放的是一些自定義控制元件

image-20201011122020373

五、效能分析

通過大廠滴滴的開源效能工具平臺,DoKit,接入App,進行效能的進一步分析,如下:

5.1 程式效能

使用APP的出題功能,分別生成1000道,2000道,10000道題目不同的消耗記憶體情況

  • 生成1000道題目時,只需要130MB
  • 生成2000道題目時,只需要155MB
  • 生成10000道題目的時候,就需要1005MB了

可以認為題目的數量影響著App的執行時所消耗的資源及記憶體,並隨著題目的數量成正比例關係。

image-20201010231808068 image-20201010231844289

image-20201010231855561

cpu消耗如下:

image-20201011001304759

頁面開啟跳轉的時長:

image-20201011001331114

5.2 效能優化

首輪優化

image-20201011010219147

可以看出執行緒池的執行緒配置對於大資料量非常重要,從生成10000條題目為例子,在4個執行緒在併發的建立物件的時候,因為設定的單個執行緒任務量為2000,無法完成剩下的2000任務,只能等4個執行緒任務都完成後再建立新執行緒執行任務。

解決方案:調整執行緒池的配置,提高執行緒的數量,提高每個生成題目的執行緒的任務數量
優化後的結果:

執行緒池複用執行緒,分頁載入50條資料,逐步的載入出全部的資料,分散記憶體和CPU的密集消耗。

優化後的生成10000條資料,耗時從10秒提高到了7秒,提升了30%。如果繼續對執行緒池的配置進行優化,效率還會繼續提高。

image-20201011010812858

進一步優化

但是對於移動應用來說,載入時間的耗時始終是大問題,讓使用者覺得等待時間過長,同時會有卡頓的問題,亟待解決,所以下面要通過效能分析工具,分析哪個步驟流程才是在生成題目列表的過程中造成卡頓呢??通過DoKit的卡頓堆疊分析圖:

image-20201011001457567

image-20201011001632545

可以看出卡頓 與 開啟頁面,建立對應資料,資料建立後,列表UI開始載入資料, 頻繁大量地建立View物件息息相關。

進一步解決方案:分頁載入

資料量過大的時候,其實不需要一口氣全部載入進去,可以採用分頁載入的方式載入與建立物件,吞吐量減少的同時CPU和記憶體的佔用也降低了。從另一個角度講,頁面也無必要一口氣載入上千條乃至萬條的資料 ( 螢幕就這麼大 ,使用者看不過來)

設定分頁載入題目的方式,每頁固定數量為 50 ,若所需數量不足50條,則繼續載入所需的數量題目。

開啟效能檢測平臺的監控 CPU,記憶體 ,幀率

未載入資料前:

image-20201011104239338

開啟頁面,載入10000條資料:

image-20201011104308452 image-20201011104425338

可以看出,優化後,載入大資料,對頁面的影響小了很多,CPU和記憶體的短時間消耗急劇降低,對App這種只擁有固定記憶體(少量)的程式,可以有很不錯的效果。

六、APP真機測試報告

6.1 測試1:程式生成四則計算題和答案是否符合題目要求

2020-10-11_12-4-36

結果說明:

以上為測試生成100道數值10以內的題目的截圖

可以得出結論,看到題目是符合去重的要求且答案是正確的

6.2 測試2:程式是否能正確判斷使用者輸入答案的正確性

以 6 道題目為例子,檢視練習情況報告,可以看到可以給出正確答案提示,以及完成的情況,錯對情況,得分率,很詳細。

image-20201011120046984

6.3 測試3:能否支援一萬道題目的生成與顯示

在出題模式下執行生成10000道數值範圍在5以內的題目的功能

image-20201011115726814 image-20201011115734721

最終效果

image-20201011122643614 image-20201011122651433
首頁 題目列表
image-20201011133555193 image-20201011133603583
練習做題 練習情況報告

七、總結

專案小結

獲得的經驗:

雖說只是做了一個小工程,從中也學習到了多執行緒的相關知識,資料結構的使用和APP介面的設計與佈局。

不足的地方:

沒有做檢視歷史做題的歷史記錄。

生成大批量的題目的時候,會隨著頁面載入越來越多的題目,隨著RecyclerView持有的子itemView數量逐漸增多會導致分頁載入,RecyclerView時會有卡頓感。

結對感受

期待ing,個人感覺是各司其職,對方請教時再給建議,要結合實際情況,不過分追求效果。

相關文章