1. 程式人生 > >【藝術探索筆記】第 15 章 Android 效能優化

【藝術探索筆記】第 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
    ="vertical">
    <include layout="@layout/view_toolbar_with_textview" /> ... </LinearLayout>

    通過這種方式,就可以不用再寫一遍重複的佈局了。

    <include> 標籤只支援 android:layout_ 開頭的屬性,比如 android:layout_width、android:layout_heightandroid: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 可以做到在使用的時候再載入,提高了程式初始化的效能。

    image

    載入方式:通過 setVisibility(View.VISIBLE) 方法或者通過 inflate() 方法。

15.1.2 繪製優化

指 View 的 onDraw 方法要避免執行大量的操作。

  1. onDraw 方法中不要建立新的區域性物件,因為 onDraw 方法可能會被頻繁呼叫,這樣會在一瞬間產生大量的臨時物件,佔用了過多的記憶體而且會導致系統頻繁出發 gc,降低了效率。

  2. onDraw 方法中不要做耗時任務,也不能執行成千上萬次的迴圈操作。大量的迴圈操作搶佔 cpu 的時間片造成 View 繪製不流暢。

15.1.3 記憶體洩漏優化

最常見的記憶體洩漏:非靜態的 Handler 物件引起記憶體洩漏;匿名內部類引起記憶體洩漏;非靜態內部類引起記憶體洩漏。

  1. 非靜態的 Handler 物件引起記憶體洩漏

    可以自定義 Handler 靜態內部類,如果需要用到外部類的變數或方法,就用弱引用去持有外部類,當需要呼叫外部類的方法的時候,從弱引用獲取外部類的例項再操作。

  2. 匿名內部類引起記憶體洩漏

    比如在設定一個 view 的點選事件的時候,如果直接在 setOnClickListener() 方法傳 listener 引數的時候,直接建立一個 listener 傳進去,這種就叫匿名內部類。解決方法是,自定義 listener 靜態內部類,用弱引用持有外部類例項。

  3. 非靜態內部類引起記憶體洩漏

    解決方法是,改為靜態內部類

有些無限迴圈的動畫也會引起記憶體洩漏,當要退出頁面時,應當手動關閉無限迴圈的動畫

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 的程式設計思想,主旨是如何提高程式碼的可維護性和可擴充套件性。

切入點:程式碼風格、程式碼的層次性和單一職責原則、面向擴充套件程式設計以及設計模式。

可讀性是程式碼可維護性的前提,而良好的程式碼風格在一定程度上可以提高程式的可讀性。

良好的程式碼風格:

  1. 命名要規範,能正確的傳達出變數或者方法的含義,少用縮寫。變數字首可以參考 Android 原始碼,比如私有成員以 m 開頭,靜態成員以 s 開頭,常量全部用大寫字母表示等等。

  2. 程式碼排版上要留出合理的空白來區分不同的程式碼塊,同類變數的宣告放在一組,兩類變數之間留出一行空白作為區分。

  3. 僅為非常關鍵的程式碼添加註釋,其他地方不寫註釋,這就對變數和方法的命名風格提出了很高的要求,一個合理的命名風格可以讓讀者閱讀原始碼就像閱讀註釋一樣清晰。 程式碼的層次性和單一職責原則

    層次性是指對於一段業務邏輯,不要試圖在一個方法或者一個類中去全部實現,將它分為幾個子邏輯,每個子邏輯做自己的事情,這樣既顯得層次分明又可以分解任務實現簡化邏輯的效果。

    單一職責是和層次性相關聯的。程式碼分層以後,每一層僅關注少量邏輯,這樣就做到了單一職責。

面向擴充套件程式設計

程式的擴充套件性標誌著開發人員是否有足夠的經驗,很多時候我發保證已經做好的需求不在後面的版本發生變更。所以在寫的時候,要時刻考慮擴充套件,考慮如果這個邏輯後期改變了需要做哪些修改,以及怎樣才能降低修改的工作量,面向擴充套件程式設計會使程式具有很好的擴充套件性  

設計模式

恰當的設計模式可以提高程式碼的可維護性和可擴充套件性,但 Android 程式容易有效能瓶頸,因此要控制設計的度,不能太牽強,否則就過度設計了。  

常見的設計模式: 單例模式、工廠模式、觀察者模式等。