今天要解決的情境是這樣的
專案需要有上傳圖片的功能
而且是一次 60 張,每張大約 3 ~ 4 MB 的大圖
一次上傳這麼多,用一般的單執行緒會需要很長的時間
實測是需要 7 ~ 9 分鐘,依網路環境有所不同
所以為了有效的縮短時間
我們可以利用多執行緖的方式來上傳
但這又會牽扯到效能問題
例如:cup 撐不撐得住,memory 夠不夠用...等
說到效能,應該是滿多人會遇到的難題吧?
雖然現今的設備硬體配置都很強力
但效能的部份還是需要去優化的
除了平時寫 code 有良好意識之外
也可以利用今天的主角 Profiler 來檢查硬體的使用程度
Profiler 是 Android Studio 內建的一個功能
可以用來監控 app 運行時的狀態
例如:network、cup、memory、energy...等。
他位於 Android Studio 的下方功能列
只要運行 app 的時候切到 Profiler 就可以得到運行中的資訊
會以一個橫向時間軸的方式呈現,如下圖
最上方的紅點則是 user 的動作
包括點擊、長按、輸入文字…等都能顯示在上面
再來是基本的四項功能
每一項點擊進去還會有更細的項目
像是 memory 裡,不僅有 memory 使用的區分
還有更細的每項使用狀況
或是像 cup 中,除了使用效率之外
還有 thread 這種更進階的資訊可以查看
這次的問題我們只會用到 cpu、memory & network,所以就不多說了
有想知道更多細節的朋友,我們可以到官網查看詳細內容喔
注意:Energy 只在 Android 8 ( api 26 ) 以上才支援喔
介紹完了 Profiler 我們回到專案
一開始改 multiple threads 上傳時
magic !
只需要 20 ~40 秒
這神奇的速度也太誇張
但不久後就有副作用了
在較低階的手機上會造成 crash
主因是效能跟不上
用 Proffiler 就可以很清楚看出來
藍色部份在網路運作不久之後就飆到一個高峰
原本 3xx 的 memory 使用量,升到 11XX MB
導致低階裝置無法負荷
猜想是一時間大量的 thread 啟動造成的峰值
點進 cpu 查看更詳細的 thread 就可以證明猜測了
app 一次開了 5x 個 thread 在上傳
難怪會飆這麼高
知道原因了之後要怎麼優化呢?
我在發 API 的地方做了點手腳
Single.just(0)
.delay(100, TimeUnit.MILLISECONDS)
// 預防 Thread 過多
// 使用 RxJava delay thread pool 最多為 10 threads
利用 delay 讓 thread 平均建立
這樣做的結果,thread 就不會在瞬間被啟動出來
而且早先上傳完的 thread 還會被釋放掉
再回 Profiler 觀察一次
這次 thread 只增加了 10 條
而 memory 最高從 11xx 降到了 4xx
不過速度就慢一點
要花之前的 2 倍時間,約 40 ~ 80 秒
但為了安全,也是值得的
======================
其實還有一個作法
用 ActivityManager.MemoryInfo
這是 Android 官方提供的 MemoryInfo 接口
我們可以利用它去查 memory 相關的資訊
比如:
public long availMem -> 可用 memory
public boolean lowMemory -> 是否處於低 memory 狀態
public long totalMem -> 總 memory
利用這些資訊我們就可以控制同時間 thread 的多寡
總合以上兩個方法就會變成這樣
ActivityManager.MemoryInfo mi =
new ActivityManager.MemoryInfo();
ActivityManager activityManager =
(ActivityManager) getSystemService(ACTIVITY_SERVICE);
activityManager.getMemoryInfo(mi);
while (mi.lowMemory){
Log.d("系統忙錄中,等待一下再執行新 thread");
Thread.sleep(200);
}
cd.add(Single.just(0)
.delay(100, TimeUnit.MILLISECONDS)
這麼做的話在系統認為自己處於 low memory status 時
我們就會先等前面的 thread 跑完
等系統覺得有空時才會接著下面的任務
======================
補充:
Rxjava 的 delay 會在內建的一個 thread pool 取 thread 出來跑
這個 pool 只有 10 條 threads
都在使用的話,會等前面的跑完才跑下一個 task
因此達到 thread 控管的目的
若不想要這麼被動
比如說 thread 想要有 20 條同時跑
由於此 thread pool size 並沒有接口可以使用
故可以使用 Rxjava 的 intervalRange 來代替 for loop
Observable.intervalRange(
0
,listFilePaths.size()
,0
,100
,TimeUnit.MILLISECONDS)
說明一下參數
1、開始的 index
2、運行的次數
3、第一次的 delay
4、每次的間隔
5、時間單位
此法就需要自己計算時間與 thread 的關係來控制 thread
比如說一個 thread 需要 2 秒完成工作
那每次間隔設為 0.1 秒,則總數 「大約」會在 20 threads
那每次間隔設為 0.2 秒,則總數 「大約」會在 10 threads
以此類推
特別標記「大約」
是由於 thread 完成工作的秒數會依硬體或網路狀態而浮動
資料來源:
https://developer.android.com/studio/profile
https://developer.android.com/reference/android/app/ActivityManager.MemoryInfo