標籤:為什麼 添加 串列 隱式 ddc 回收 多線程 位置 選擇
-
- 前言
- Android 開發中多線程的必要性
- 理解 Android 多線程
- MessageQueue
- Looper
- Handler
- HandlerThread
- Android 中為什麼只允許在主線程更新 UI
- Android 系統為了避免過度複雜的安全執行緒問題特地規定只允許在主線程中更新 UI
- 而開發人員為了避免上述問題需要注意的是
- 總結
- Thanks
前言
Android Performance Patterns Season 5 主要介紹了 Android 多線程環境下的效能問題。通過介紹 Android 提供的多種多線程工具類 (AsyncTask, HandlerThread, IntentService, ThreadPool),讓我們熟悉各個組件的適用情境,從而在特定情境下選擇效能最好的一個。
本文為觀看視頻 1 ~ 3 節,參考 胡凱的 Android 效能最佳化典範第 5 季 總結所得,感謝他們。
Android 開發中多線程的必要性
Android 開發中,許多操作都需要由 主線程(UI 線程)來執行,比如:
- 系統事件(例如裝置狀態變動)
- 輸入事件
- 服務
- 鬧鐘
- UI 繪製
- …
我們經常需要針對這些情況編寫代碼。
由於主線程只有一個,所有任務都是串列執行,如果我們在某個操作中包含大量的網路請求、I/O,將會影響後續使用者後續操作。
使用者感知最明顯的就是介面繪製、響應是否及時:
我們知道 Android 系統的螢幕重新整理頻率為 60 fps, 也就是每隔 16 ms 重新整理一次。如果在某次繪製過程中,我們的操作不能在 16 ms 內完成,那它則不能趕上這次的繪製公交車,只能等下一輪,這種現象叫做 “掉幀”,使用者看到的就是介面繪製不連續、卡頓。
為了避免耗時較久的操作導致 “掉幀”,我們會把這些操作從主線程執行換到子線程,這樣主線程的其他動作不會受到影響,使用者體驗也會流暢許多。
理解 Android 多線程
一個線程,主要有三個狀態:開始、執行任務、結束。
當線程存活期間,我們會讓它執行大量的任務,當任務完成或者主動取消時,線程功成身退。
很多情況下,我們會有很多線程同時存活、執行任務,這時需要添加一個 任務隊列,讓線程不停地從隊列中擷取任務,同時有其他線程向其中新增工作,典型的 生產者-消費者 模型:
如果我們來實現這個模型,需要寫三個角色:生產者線程、消費者線程、任務隊列,同時還要保證它們的協作有條不紊,這可能會難倒一大堆人。
為了讓開發人員更省心,Android 系統替我們實現了上述類,分別是:
- MessageQueue
- Looper
- Handler
MessageQueue
MessageQueue 就是任務隊列,儲存著不同類型任務的載體 (Intent, Runnable, Message)。
Looper
Looper 就是我們所說的 “消費者”,它不停地從任務隊列中擷取任務並執行。
Handler
Handler 就是 “生產者”,它把任務從其他線程送到 MessageQueue 中。
Handler 可以指定任務在任務隊列中的位置,也可以按照一定的時間延遲送貨。
HandlerThread
HandlerThread 就是上述三個組件的組合。
每個應用啟動時,系統會建立一個該應用的進程以及主線程,這裡的主線程就是一個 HandlerThread。
這個主線程會處理主要事件,具體內容:
Android 中為什麼只允許在主線程更新 UI
Android 系統中,預設只能在 主線程(UI 線程)更新 UI,當你在 子線程進行 UI 修改時,可能不起作用甚至是奔潰:
為什麼要這樣設計呢?
我們知道,多線程並發訪問資源要遵循重要的原則就是 原子性、可見度、有序性。沒有同步機制的情況下,多個線程同時讀寫記憶體可能會導致意料之外的問題:
多線程同時操作 UI 也一樣,如果想要允許多個線程更新 UI,就要設計對應的同步機制,為了避免這種問題,Android 系統直接規定只允許在 UI 線程更新 UI。
除了安全執行緒外,還有個原因: UI 組件的生命週期並不確定。
可能有這種情況:我們在某個執行網路請求的線程中持有一個 Button 的引用,然而在請求結果返回之前,這個 button 被 View Hierarchy 移除,這時對 button 的任何操作都不可用,並且也沒有意義了。
此外還有一點 線程引用導致的記憶體流失問題。
我們知道每個 View 都持有當前 Context, Activity 的引用,如果子線程持有某個 View 的引用,繼而持有了對應 Activity 引用,那麼線上程返回之前,即使該 Activity 不可用,也無法回收,這就造成了 記憶體流失。
除了持有 View,線程隱式持有 Activity 也可能導致記憶體流失,只要子線程沒有結束,參考關聯性就一直存在。
比如在 Activity 中建立個內部 AsyncTask:
或者是常見的在 Activity 裡建立個 Handler:
正如 Android Studio 提示的那樣,內部線程工具類持有外部類引用,可能會導致 記憶體流失。
Android 系統為了避免過度複雜的安全執行緒問題,特地規定只允許在主線程中更新 UI。而開發人員,為了避免上述問題,需要注意的是:
不要再任何子線程持有 UI 組件或者 Activity 的引用。
總結
本文大概介紹了 Android 中多線程的必要性以及一些基礎概念。
Android 系統為我們提供了以下幾種工具類:
- AsyncTask
- HandlerThread
- ThreadPool
- IntentService
- 在子線程中擷取 Intent,用於執行由 UI 出發的後台 Service
接下來我們將跟隨官方視頻逐漸瞭解這幾個工具類的特點,從而能夠在合適的情境下選擇對的人,儘可能地最佳化應用的效能。
感謝關注。
Thanks
https://www.youtube.com/watch?v=qk5F6Bxqhr4&index=1&list=PLWz5rJ2EKKc9CBxr3BVjPTPoDPLdPIFCE&t=39s
http://hukai.me/android-performance-patterns-season-5/
Android 效能最佳化:多線程注