1. 程式人生 > >Android 效能優化:多執行緒

Android 效能優化:多執行緒

前言

Android Performance Patterns Season 5 主要介紹了 Android 多執行緒環境下的效能問題。通過介紹 Android 提供的多種多執行緒工具類 (AsyncTask, HandlerThread, IntentService, ThreadPool),讓我們熟悉各個元件的適用場景,從而在特定場景下選擇效能最好的一個。

Android 開發中多執行緒的必要性

這裡寫圖片描述

Android 開發中,許多操作都需要由 主執行緒(UI 執行緒)來執行,比如:

  • 系統事件(例如裝置狀態變動)
  • 輸入事件
  • 服務
  • 鬧鐘
  • UI 繪製

我們經常需要針對這些情況編寫程式碼。

shixinzhang

由於主執行緒只有一個,所有任務都是序列執行,如果我們在某個操作中包含大量的網路請求、I/O,將會影響後續使用者後續操作。

使用者感知最明顯的就是介面繪製、響應是否及時

這裡寫圖片描述

我們知道 Android 系統的螢幕重新整理頻率為 60 fps, 也就是每隔 16 ms 重新整理一次。如果在某次繪製過程中,我們的操作不能在 16 ms 內完成,那它則不能趕上這次的繪製公交車,只能等下一輪,這種現象叫做 “掉幀”,使用者看到的就是介面繪製不連續、卡頓

shixinzhang

為了避免耗時較久的操作導致 “掉幀”,我們會把這些操作從主執行緒執行換到子執行緒,這樣主執行緒的其他操作不會受到影響,使用者體驗也會流暢許多。

理解 Android 多執行緒

這裡寫圖片描述

一個執行緒,主要有三個狀態:開始、執行任務、結束。

shixinzhang

當執行緒存活期間,我們會讓它執行大量的任務,當任務完成或者主動取消時,執行緒功成身退。

很多情況下,我們會有很多執行緒同時存活、執行任務,這時需要新增一個 任務佇列,讓執行緒不停地從佇列中獲取任務,同時有其他執行緒向其中新增任務,典型的 生產者-消費者 模型:

shixinzhang

如果我們來實現這個模型,需要寫三個角色:生產者執行緒、消費者執行緒、任務佇列,同時還要保證它們的協作有條不紊,這可能會難倒一大堆人。

為了讓開發者更省心,Android 系統替我們實現了上述類,分別是:

  • MessageQueue
  • Looper
  • Handler

MessageQueue

這裡寫圖片描述

MessageQueue 就是任務佇列,儲存著不同型別任務的載體 (Intent, Runnable, Message)。

Looper

shixinzhang

Looper 就是我們所說的 “消費者”,它不停地從任務佇列中獲取任務並執行。

Handler

shixinzhang

Handler 就是 “生產者”,它把任務從其他執行緒送到 MessageQueue 中。

Handler 可以指定任務在任務佇列中的位置,也可以按照一定的時間延遲送貨。

HandlerThread

shixinzhang

HandlerThread 就是上述三個元件的組合。

每個應用啟動時,系統會建立一個該應用的程序以及主執行緒,這裡的主執行緒就是一個 HandlerThread。

這個主執行緒會處理主要事件,具體內容如圖所示:

這裡寫圖片描述

Android 中為什麼只允許在主執行緒更新 UI

Android 系統中,預設只能在 主執行緒(UI 執行緒)更新 UI,當你在 子執行緒進行 UI 修改時,可能不起作用甚至是奔潰:

這裡寫圖片描述

為什麼要這樣設計呢?

我們知道,多執行緒併發訪問資源要遵循重要的原則就是 原子性、可見性、有序性。沒有同步機制的情況下,多個執行緒同時讀寫記憶體可能會導致意料之外的問題:

shixinzhang

多執行緒同時操作 UI 也一樣,如果想要允許多個執行緒更新 UI,就要設計對應的同步機制,為了避免這種問題,Android 系統直接規定只允許在 UI 執行緒更新 UI。

除了執行緒安全外,還有個原因: UI 元件的生命週期並不確定

shixinzhang

可能有這種情況:我們在某個執行網路請求的執行緒中持有一個 Button 的引用,然而在請求結果返回之前,這個 button 被 View Hierarchy 移除,這時對 button 的任何操作都不可用,並且也沒有意義了。

此外還有一點 執行緒引用導致的記憶體洩漏問題

我們知道每個 View 都持有當前 Context, Activity 的引用,如果子執行緒持有某個 View 的引用,繼而持有了對應 Activity 引用,那麼線上程返回之前,即使該 Activity 不可用,也無法回收,這就造成了 記憶體洩漏。

除了持有 View,執行緒隱式持有 Activity 也可能導致記憶體洩漏,只要子執行緒沒有結束,引用關係就一直存在。

比如在 Activity 中建立個內部 AsyncTask:

shixinzhang

或者是常見的在 Activity 裡建立個 Handler:

shixinzhang

正如 Android Studio 提示的那樣,內部執行緒工具類持有外部類引用,可能會導致 記憶體洩漏

Android 系統為了避免過度複雜的執行緒安全問題,特地規定只允許在主執行緒中更新 UI。

而開發者,為了避免上述問題,需要注意的是:

不要再任何子執行緒持有 UI 元件或者 Activity 的引用。

總結

本文大概介紹了 Android 中多執行緒的必要性以及一些基礎概念。

Android 系統為我們提供了以下幾種工具類:

  • AsyncTask
    • 主執行緒、子執行緒間任務的切換
  • HandlerThread
    • 為某個任務/回撥單獨開一個執行緒
  • ThreadPool
    • 管理多個執行緒,併發執行任務
  • IntentService
    • 在子執行緒中獲取 Intent,用於執行由 UI 出發的後臺 Service

shixinzhang

接下來我們將跟隨官方視訊逐漸瞭解這幾個工具類的特點,從而能夠在合適的場景下選擇對的人,儘可能地優化應用的效能。

感謝關注。

Thanks