close

今天要解決的情境是這樣的

專案需要有上傳圖片的功能

而且是一次 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

 

 

 


arrow
arrow

    顏澤偉 發表在 痞客邦 留言(0) 人氣()