這就是小學生也會用的四則計算練習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 區德明 完成
功能測試與故障修復 測試程序的功能,修復出現的故障 區德明 完成
性能分析與優化 分析程序執行的性能,優化性能表現 區德明 完成

結構圖

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,個人感覺是各司其職,對方請教時再給建議,要結合實際情況,不過分追求效果。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM