在 Android 上有效利用執行緒能夠幫助您加強應用程式的 才需進行本頁說明使用執行緒的幾個層面: 操作 UI,或稱主執行緒應用程式生命週期和 執行緒優先順序以及平台提供的方法 協助管理執行緒 變得複雜本頁面會說明這些領域中可能存在的陷阱 避開這些概念的策略
主要執行緒
當使用者啟動應用程式時,Android 會建立新的 Linux 程序與執行作業執行緒。這個主要執行緒 也稱為 UI 執行緒,會負責處理 。瞭解運作方式有助於您設計應用程式來使用 為獲得最佳效能
內部原理
主要執行緒的設計非常簡單:只負責執行和執行工作 執行緒安全工作佇列中的工作封鎖區塊,直到其應用程式終止為止。 這個架構會從多種來源產生部分上述的作業區塊,這些 包括與生命週期資訊相關的回呼、使用者事件, 或是來自其他應用程式和程序的事件此外,應用程式 明確地將區塊加入佇列,不使用架構。
幾乎不限 應用程式執行的程式碼區塊會和事件回呼 (例如輸入、 版面配置加載或繪圖當事件觸發事件時,事件的執行緒會位於事件的執行緒。 系統會將事件從本身推送到主要執行緒的訊息中 佇列。隨後主要執行緒即可處理該事件。
當系統更新動畫或畫面時,系統會嘗試執行 系統每隔 16 毫秒執行一次工作 (負責繪製畫面) 一次, 第 60 個百分位數的轉譯器60 每秒影格數。為了讓系統達到這個目標,UI/View 階層 必須在主執行緒上更新。不過,如果主要執行緒訊息佇列中的工作太多或太長,導致主要執行緒完成更新的速度過慢,那麼應用程式應將這項工作移到背景工作執行緒。如果主執行緒無法在 16 毫秒內完成工作區塊的執行作業, 使用者可能會發現停頓、延遲,或輸入的 UI 回應度不足等問題。 如果主執行緒封鎖大約五秒的時間,系統會顯示 應用程式 無回應 (ANR) 對話方塊,可讓使用者直接關閉應用程式。
將太多或太長的工作移出主要執行緒,這些工作就不會干擾使用者輸入內容的轉譯順暢度和回應速度,因此強烈建議您在應用程式中採用執行緒。
執行緒和 UI 物件參照
Android View 物件不符合執行緒安全規定。應用程式應會持續建立、使用 刪除主執行緒上的 UI 物件如果您嘗試 甚至在主執行緒以外的執行緒中參照 UI 物件,就會產生結果 可能是因為例外狀況、無聲失敗、當機以及其他未定義的錯誤行為
參考檔案的問題可分為兩類:明確參照 和隱含參照
明確參照
許多非主要執行緒工作的最終目標都是更新 UI 物件。 不過,一旦其中一個執行緒在檢視區塊階層存取物件,就可能導致應用程式有不穩定的情形:如果背景工作執行緒在其他執行緒參照某物件的時候,變更了該物件的屬性,就會產生未定義的結果。
舉例來說,假設某應用程式在背景工作執行緒上直接參照某 UI 物件。背景工作執行緒上的物件可能含有
View
;但在工作完成前,View
是
已從檢視區塊階層中移除。當這兩種動作同時發生時
參照會將 View
物件保留在記憶體中,並為該物件設定屬性。
不過,使用者看不到
此物件,而應用程式在物件的參照消失後,也會刪除該物件。
在另一個範例中,View
物件包含活動的參照
所屬的資源如果
系統就會刪除活動,但依循執行緒的工作區塊
無論直接或間接引用,垃圾收集器都不會收集
直到該工作區塊完成執行為止
在發生執行緒工作的情況下,這個狀況可能會導致發生問題
發生某些活動生命週期事件 (例如螢幕旋轉) 時。
系統在傳輸期間才能執行垃圾收集
作業完成。因此,可能會有兩個 Activity
物件
直到開始垃圾收集為止
在上述情況下,我們不建議您的應用程式含有煽情露骨內容 執行緒工作任務中 UI 物件的參照。請避免使用這種參照方式,有助於防範這些類型的記憶體流失和執行緒爭用情形。
在所有情況下,應用程式都應只更新主執行緒上的 UI 物件。這個 這表示你必須制定協商政策,讓多個執行緒可以 傳回工作給主執行緒,而這些執行緒的工作就是最頂層的活動,或 含有更新實際 UI 物件的片段。
隱含參照
執行緒物件常見的程式碼設計問題,可以 程式碼如下:
Kotlin
class MainActivity : Activity() { // ... inner class MyAsyncTask : AsyncTask<Unit, Unit, String>() { override fun doInBackground(vararg params: Unit): String {...} override fun onPostExecute(result: String) {...} } }
Java
public class MainActivity extends Activity { // ... public class MyAsyncTask extends AsyncTask<Void, Void, String> { @Override protected String doInBackground(Void... params) {...} @Override protected void onPostExecute(String result) {...} } }
這個程式碼片段的缺點是,程式碼宣告了執行緒物件
MyAsyncTask
做為某些活動的非靜態內部類別 (或內部類別)
)。這項宣告會為內含的 Activity
建立隱含參照
執行個體。因此,物件會包含活動的參照,直到
執行緒工作完成,導致參照活動發生延遲刪除。
這段延遲時間會進一步對記憶體造成壓力。
如要直接解決這個問題,您可以定義超載類別 執行個體,或儲存在各自的檔案中,因此 隱含參照
您也可以隨時在適當時機取消並清除背景工作
Activity
生命週期回呼,例如 onDestroy
。不過,這種方法相當繁瑣,又容易出錯。一般而言,建議您不要使用複雜且非 UI 的邏輯
直接在活動中操作此外,AsyncTask
現已淘汰,
這種做法並不適用於新的程式碼。請參閱「Android 的執行緒作業」。
,進一步瞭解您可以使用的並行基本功能。
執行緒和應用程式活動生命週期
應用程式生命週期可能會影響應用程式執行緒的運作方式。 您可能需要決定系統在刪除活動後,應不應該保留執行緒。您也應該瞭解 執行緒優先順序,以及活動是在前景執行或 背景。
保留執行緒
和活動的生命週期相較,這些活動產生的執行緒可以保留更長的時間。執行緒 會繼續繼續執行,無論應用程式建立或刪除作業為何, 活動,不過系統會在使用者沒有回應時,一併終止這些活動 以及更多活躍的應用程式元件在某些情況下,這種持續性是理想的做法。
假設某活動產生了一系列透過執行緒執行的工作區塊,然後系統在背景工作執行緒可執行區塊之前,就已經刪除該活動。應 運作中的區塊?
如果該區塊將要更新已不存在的 UI,就沒有理由繼續執行這項工作。舉例來說,如果工作內容是載入使用者資訊 更新檢視畫面後,就不再需要使用執行緒。
相較之下,未與 UI 完全相關的工作封包可能會有助益。在這種情況下,您應該繼續保留執行緒。舉例來說,封包可能是
然後等待下載映像檔、快取到磁碟,然後更新相關聯的
View
物件。雖然物件已不存在,但對物件而言
在某些情況下,快取圖片可能還是會有幫助,以防使用者返回
刪除活動。
如要手動管理所有執行緒物件的生命週期回應,
極為複雜如果您不正確管理這些回應,應用程式
記憶體爭用情形和效能問題結合 ViewModel
和 LiveData
可讓您載入資料,並在發生變更時收到通知,不必擔心生命週期。ViewModel
物件
才能解決這個問題ViewModels 可以在設定變更時保留,
可讓您輕鬆保存檢視資料。如要進一步瞭解 ViewModel,請參閱
ViewModel 指南,並進一步瞭解
LiveData 請參閱 LiveData 指南。如果發生以下情況:
想進一步瞭解應用程式架構,請參閱
應用程式架構指南。
執行緒優先順序
如「程序和應用程式生命週期」所述,應用程式執行緒接收的優先順序,一部分取決於應用程式本身所處的生命週期階段。在應用程式中建立和管理執行緒時,請務必為執行緒設定優先順序,讓正確的執行緒在適當時機收到合適的優先順序。如果優先順序設定過高,執行緒就可能打斷 UI 執行緒和 RenderThread,導致應用程式遺失影格。如果設定過低,則可能導致載入圖片等非同步工作速度過慢。
每次建立執行緒時,都應呼叫 setThreadPriority()
。系統的執行緒排程器會先處理高優先順序的執行緒,並根據需求排定優先順序,最終完成所有工作。一般來說,前景群組中的執行緒可大約占 95% 的裝置總執行時間,背景群組則大約占 5%。
系統也會使用 Process
類別,為每個執行緒指派專用的優先順序值。
根據預設,系統會將執行緒的優先順序,設為與產生執行緒相同的優先順序和群組成員。不過,應用程式可以使用 setThreadPriority()
,明確調整執行緒優先順序。
Process
類別可提供一組常數,供應用程式用於設定執行緒優先順序,降低指派優先順序值的複雜度。舉例來說,THREAD_PRIORITY_DEFAULT
代表執行緒的預設值。如果執行緒正在執行較不緊急的工作,應用程式應將執行緒的優先順序設為 THREAD_PRIORITY_BACKGROUND
。
應用程式可以使用 THREAD_PRIORITY_LESS_FAVORABLE
和 THREAD_PRIORITY_MORE_FAVORABLE
常數,當做設定相關優先順序的遞增依據。如需執行緒優先順序清單,請查看 Process
類別內的 THREAD_PRIORITY
常數。
如要進一步瞭解如何管理執行緒,請參閱有關 Thread
和 Process
類別的說明文件。
執行緒輔助類別
如果開發人員使用 Kotlin 做為主要語言,建議使用協同程式。協同程式可提供多項好處,包括不使用回呼即可編寫非同步程式碼,以及適用於調整範圍、取消,以及錯誤處理作業的結構化並行做法。
這個架構也提供同樣的 Java 類別和基本功能來處理執行緒作業,例如 Thread
、Runnable
和 Executors
類別,此外還提供了其他類別,例如 HandlerThread
。詳情請參閱「Android 執行緒作業」。
HandlerThread 類別
處理常式執行緒是長時間執行的執行緒,可有效從佇列取得工作,並在佇列上執行作業。
如要從 Camera
物件取得預覽影格,請思考以下這個常見難題。註冊相機預覽影格時,您會在 onPreviewFrame()
回呼中收到這些影格,而系統會在呼叫此回呼的事件執行緒上叫用此回呼。如果是在 UI 執行緒叫用此回呼,那麼處理大量像素陣列的工作就會干擾轉譯程序和事件處理工作。
在本範例中,當應用程式將 Camera.open()
指令委派至處理常式執行緒上的工作區塊時,相關聯的 onPreviewFrame()
回呼不會在 UI 執行緒上,而會在處理常式執行緒上。因此,如要在像素上長時間執行工作,這個解決方案可能比較合適。
應用程式使用 HandlerThread
建立執行緒時,請務必根據該執行緒的工作類型設定執行緒優先順序。請注意,CPU 只能平行處理少量執行緒。設定優先順序可在所有其他執行緒爭用資源時,幫助系統瞭解如何適當安排工作。
ThreadPoolExecutor 類別
某些工作類型可以簡化為高度平行處理的分散式工作,例如為 800 萬像素圖片的每個 8x8 區塊計算濾鏡。這項作業會建立大量工作封包,因此不適合使用 HandlerThread
類別。
使用 ThreadPoolExecutor
輔助類別可更輕鬆執行這項程序。這個類別可管理執行緒群組的建立程序、設定優先順序,並管理系統將工作分配給執行緒的做法。為因應工作負載的增減情形,這個類別可啟動或刪除更多執行緒。
這個類別也可幫助應用程式產生理想數量的執行緒。在這個類別建構 ThreadPoolExecutor
物件時,應用程式會設定執行緒數量的下限和上限。提供給 Pod 的工作負載
ThreadPoolExecutor
增加,
這個類別會採用初始化的執行緒數量下限和上限
,並考量待處理工作的數量。根據這些
影響範圍取決於ThreadPoolExecutor
因素
執行緒應隨時處於運作狀態。
應建立多少執行緒?
就軟體層級而言,程式碼可以建立數百個執行緒,但可能會造成效能問題。應用程式共享有限 CPU 資源的對象,包括背景服務、轉譯器、音訊引擎、網路等更多項目。CPU 一次只能平行處理少量執行緒,一旦超過限制,就會發生優先順序和排程問題。因此,請務必只根據工作負載需求,建立適當數量的執行緒。
造成影響的變數有很多,但挑選一個值 (例如從 4 開始),然後使用 Systrace 測試這個值也是同樣有效的策略。您可以採用試錯法,找出不會發生問題的執行緒數量下限。
決定執行緒數量時,也需要考量執行緒占用記憶體的成本。每個執行緒最少需要消耗 64,000 記憶體。隨著裝置上安裝多個應用程式,這個數值會快速增加,尤其是呼叫堆疊大幅擴增時。
許多系統程序和第三方程式庫通常會啟動專屬的執行緒集區。如果應用程式可重複利用現有的執行緒集區,就能減少記憶體和處理資源的爭用情形,協助提升效能。