【藝術探索筆記】第 15 章 Android 效能優化
第 15 章 Android 效能優化
Android 裝置作為一種移動裝置,不管是記憶體還是 CPU 的效能都受到了一定的限制,無法像 PC 那樣具有超大的記憶體和高效能的 CPU。所以 Android 程式不可能無限制的使用記憶體和 CPU 資源,過多的使用記憶體會導致程式記憶體溢位,即 OOM。過多的使用 CPU 資源,一般指做大量耗時任務,會導致手機卡頓程式無響應,即 ANR。
15.1 Android 的效能優化方法
佈局優化、繪製優化、記憶體洩漏優化、響應速度優化
15.1.1 佈局優化
思想就是儘量減少佈局檔案的層級。層級少了,Android 繪製時的工作量少了,效能自然就高了。
首先刪除佈局中無用的控制元件和層級。比較複雜的佈局,比如需要巢狀才能實現的,使用 ConstraintLayout 來實現,完美消除巢狀,代替 RelativeLayout。FrameLayout 和 LinearLayout 都是一種簡單高效的 ViewGroup。
佈局優化的另一種手段: <include>
標籤、<merge>
標籤和 ViewStub。<include>
標籤用於佈局重用;<merge>
標籤和 <include>
配合使用,可以減少佈局的層級;ViewStub 提供了按需載入的功能,當需要時才會將 ViewStub 中的佈局載入到記憶體,提高了程式的初始化效率。
<include>
標籤將一個指定的佈局檔案載入到當前的佈局檔案中
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:background="@color/white" android:orientation
通過這種方式,就可以不用再寫一遍重複的佈局了。
<include>
標籤只支援android:layout_
開頭的屬性,比如android:layout_width、android:layout_height
;android:id
是個特例,如果<include>
標籤指定了 id,被包含的佈局檔案跟元素也指定了 id,以<include>
指定的 id 為準。注意當
<include>
標籤指定了android:layout_
開頭的屬性,那麼要求android:layout_width、android:layout_height
必須存在,負責其他的指定的屬性無效。<merge>
標籤一般和
<include>
標籤一起使用從而減少佈局的層級。上邊示例中當前佈局是一個豎直的 LinearLayout,這是如果被包含的佈局檔案也是用了豎直的 LinearLayout,此時顯然被包含的佈局的 LinearLayout 是多餘的,可以通過<merge>
標籤來代替,去掉一層巢狀。ViewStub
繼承自 View,輕量級,寬高都是 0,因此它本身不參與任何的佈局和繪製過程。
它的意義在於按需載入所需的佈局檔案。實際開發中很多佈局檔案正常情況下不會顯示,比如載入失敗的佈局檔案,他沒必要在初始化的時候就載入進來,通過 ViewStub 可以做到在使用的時候再載入,提高了程式初始化的效能。
載入方式:通過
setVisibility(View.VISIBLE)
方法或者通過inflate()
方法。
15.1.2 繪製優化
指 View 的 onDraw 方法要避免執行大量的操作。
onDraw 方法中不要建立新的區域性物件,因為 onDraw 方法可能會被頻繁呼叫,這樣會在一瞬間產生大量的臨時物件,佔用了過多的記憶體而且會導致系統頻繁出發 gc,降低了效率。
onDraw 方法中不要做耗時任務,也不能執行成千上萬次的迴圈操作。大量的迴圈操作搶佔 cpu 的時間片造成 View 繪製不流暢。
15.1.3 記憶體洩漏優化
最常見的記憶體洩漏:非靜態的 Handler 物件引起記憶體洩漏;匿名內部類引起記憶體洩漏;非靜態內部類引起記憶體洩漏。
非靜態的 Handler 物件引起記憶體洩漏
可以自定義 Handler 靜態內部類,如果需要用到外部類的變數或方法,就用弱引用去持有外部類,當需要呼叫外部類的方法的時候,從弱引用獲取外部類的例項再操作。
匿名內部類引起記憶體洩漏
比如在設定一個 view 的點選事件的時候,如果直接在
setOnClickListener()
方法傳 listener 引數的時候,直接建立一個 listener 傳進去,這種就叫匿名內部類。解決方法是,自定義 listener 靜態內部類,用弱引用持有外部類例項。非靜態內部類引起記憶體洩漏
解決方法是,改為靜態內部類
有些無限迴圈的動畫也會引起記憶體洩漏,當要退出頁面時,應當手動關閉無限迴圈的動畫
15.1.4 響應速度優化和 ANR 日誌分析
核心思想是避免在主執行緒中做耗時操作。
Activity 5 秒未響應螢幕觸控事件或者鍵盤輸入事件就會出現 ANR;BroadcastReceiver 如果 10 秒之內未執行完操作也會出現 ANR。
定位 ANR 問題出現的地方。當程序發生 ANR 後,系統會在 /data/anr 目錄建立檔案 traces.txt(名字好像不固定),通過這個檔案就可以定位哪裡出現了 ANR。
發生 ANR 後,連線手機後執行 adb 命令:adb pull /data/anr .
,把 anr 目錄上傳到電腦,就能分析 traces.txt 檔案了
15.1.6 執行緒優化
採用執行緒池
15.1.7 一些效能優化建議
避免建立過多物件
不要過多使用列舉,列舉佔用的記憶體空間要比整型大
常量使用 static final 來修飾
使用一些 Android 特有的資料結構,如 SparseArray 和 Pair 等,它們都具有更好的效能
適當使用軟引用和弱引用
採用記憶體快取和磁碟快取
儘量採用靜態內部類,這樣可以避免潛在的由於內部類而導致的記憶體洩漏
15.2 記憶體洩漏分析
整合 LeakCanary,發生記憶體洩露後通過它可以方便地分析記憶體洩漏
15.3 提高程式的可維護性
Android 的程式設計思想,主旨是如何提高程式碼的可維護性和可擴充套件性。
切入點:程式碼風格、程式碼的層次性和單一職責原則、面向擴充套件程式設計以及設計模式。
可讀性是程式碼可維護性的前提,而良好的程式碼風格在一定程度上可以提高程式的可讀性。
良好的程式碼風格:
命名要規範,能正確的傳達出變數或者方法的含義,少用縮寫。變數字首可以參考 Android 原始碼,比如私有成員以 m 開頭,靜態成員以 s 開頭,常量全部用大寫字母表示等等。
程式碼排版上要留出合理的空白來區分不同的程式碼塊,同類變數的宣告放在一組,兩類變數之間留出一行空白作為區分。
僅為非常關鍵的程式碼添加註釋,其他地方不寫註釋,這就對變數和方法的命名風格提出了很高的要求,一個合理的命名風格可以讓讀者閱讀原始碼就像閱讀註釋一樣清晰。 程式碼的層次性和單一職責原則
層次性是指對於一段業務邏輯,不要試圖在一個方法或者一個類中去全部實現,將它分為幾個子邏輯,每個子邏輯做自己的事情,這樣既顯得層次分明又可以分解任務實現簡化邏輯的效果。
單一職責是和層次性相關聯的。程式碼分層以後,每一層僅關注少量邏輯,這樣就做到了單一職責。
面向擴充套件程式設計
程式的擴充套件性標誌著開發人員是否有足夠的經驗,很多時候我發保證已經做好的需求不在後面的版本發生變更。所以在寫的時候,要時刻考慮擴充套件,考慮如果這個邏輯後期改變了需要做哪些修改,以及怎樣才能降低修改的工作量,面向擴充套件程式設計會使程式具有很好的擴充套件性
設計模式
恰當的設計模式可以提高程式碼的可維護性和可擴充套件性,但 Android 程式容易有效能瓶頸,因此要控制設計的度,不能太牽強,否則就過度設計了。
常見的設計模式: 單例模式、工廠模式、觀察者模式等。