AndroidAPP效能優化的一些思考

技術小能手發表於2018-09-12

說到 Android 系統手機,大部分人的印象是用了一段時間就變得有點卡頓,有些程式在執行期間莫名其妙的出現崩潰,開啟系統資料夾一看,發現多了很多檔案,然後用手機管家 APP 不斷地進行清理優化 ,才感覺執行速度稍微提高了點,就算手機在各種效能跑分軟體面前分數遙遙領先,還是感覺無論有多大的記憶體空間都遠遠不夠用。相信每個使用 Android 系統的使用者都有過以上類似經歷,確實,Android 系統在流暢性方面不如 IOS 系統,為何呢,明明在看手機硬體配置上時,Android 裝置都不會輸於 IOS 裝置,甚至都強於它,關鍵是在於軟體上。造成這種現象的原因是多方面的,簡單羅列幾點如下:

     • 其實近年來,隨著 Android 版本不斷迭代,Google 提供的Android 系統已經越來越流暢,目前最新發布的版本是 Android 8.0 Oreo 。但是在國內大部分使用者用的 Android 手機系是各大廠商定製過的版本,往往不是最新的原生系統核心,可能絕大多數還停留在 Android 5.0 系統上,甚至 Android 6.0 以上所佔比例還偏小,更新存在延遲性。

     • 由於 Android 系統原始碼是開放的,每個人只要遵從相應的協議,就可以對原始碼進行修改,那麼國內各個廠商就把基於 Android 原始碼改造成自己對外發布的系統,比如我們熟悉的小米手機 Miui 系統、華為手機 EMUI 系統、Oppo 手機 ColorOS 系統等。由於每個廠商都 修改過 Android 原生系統原始碼,這裡面就會引發一個問題,那就是著名的Android 碎片化問題,本質就是不同 Android 系統的應用相容性不同,達不到一致性。

     • 由於存在著各種 Android 碎片化和相容性問題,導致 Android 開發者在開發應用時需要對不同系統進行適配,同時每個 Android 開發者的開發水平參差不齊,寫出來的應用效能也都存在不同型別的問題,導致使用者在使用過程中使用者體驗感受不同,那麼有些問題使用者就會轉化為 Android 系統問題,進而影響對Android 手機的評價。

效能優化

今天想說的重點是Android APP 效能優化,也就是在開發應用程式時應該注意的點有哪些,如何更好地提高使用者體驗。一個好的應用,除了要有吸引人的功能和互動之外,在效能上也應該有高的要求,即時應用非常具有特色,在產品前期可能吸引了部分使用者,但是使用者體驗不好的話,也會給產品帶來不好的口碑。那麼一個好的應用應該如何定義呢?主要有以下三方面:

     • 業務/功能

     • 符合邏輯的互動

     • 優秀的效能

眾所周知,Android 系統作為以移動裝置為主的作業系統,硬體配置是有一定的限制的,雖然配置現在越來越高階,但仍然無法與 PC 相比,在 CPU 和記憶體上使用不合理或者耗費資源多時,就會碰到記憶體不足導致的穩定性問題、CPU 消耗太多導致的卡頓問題等。

面對問題時,大家想到的都是聯絡使用者,然後檢視日誌,但殊不知有關效能類問題的反饋,原因也非常難找,日誌大多用處不大,為何呢?因為效能問題大部分是非必現的問題,問題定位很難復現,而又沒有關鍵的日誌,當然就無法找到原因了。這些問題非常影響使用者體驗和功能使用,所以瞭解一些效能優化的一些解決方案就顯得很重要了,並在實際的專案中優化我們的應用,進而提高使用者體驗。

四個方面

可以把使用者體驗的效能問題主要總結為4個類別:

     • 流暢

     • 穩定

     • 省電、省流量

     • 安裝包小

效能問題的主要原因是什麼,原因有相同的,也有不同的,但歸根到底,不外乎記憶體使用、程式碼效率、合適的策略邏輯、程式碼質量、安裝包體積這一類問題

打造一個高質量的應用應該以4個方向為目標:快、穩、省、小。

快:使用時避免出現卡頓,響應速度快,減少使用者等待的時間,滿足使用者期望。

穩:減低 crash 率和 ANR 率,不要在使用者使用過程中崩潰和無響應。

省:節省流量和耗電,減少使用者使用成本,避免使用時導致手機發燙。

小:安裝包小可以降低使用者的安裝成本。

要想達到這4個目標,具體實現是在右邊框裡的問題:卡頓、記憶體使用不合理、程式碼質量差、程式碼邏輯亂、安裝包過大,這些問題也是在開發過程中碰到最多的問題,在實現業務需求同時,也需要考慮到這點,多花時間去思考,如何避免功能完成後再來做優化,不然的話等功能實現後帶來的維護成本會增加。

卡頓優化

卡頓根本原因

根據Android 系統顯示原理可以看到,影響繪製的根本原因有以下兩個方面:

     • 繪製任務太重,繪製一幀內容耗時太長。

     • 主執行緒太忙,根據系統傳遞過來的 VSYNC 訊號來時還沒準備好資料導致丟幀。

繪製耗時太長,有一些工具可以幫助我們定位問題。主執行緒太忙則需要注意了,主執行緒關鍵職責是處理使用者互動,在螢幕上繪製畫素,並進行載入顯示相關的資料,所以特別需要避免任何主執行緒的事情,這樣應用程式才能保持對使用者操作的即時響應。總結起來,主執行緒主要做以下幾個方面工作:

     • UI 生命週期控制

     • 系統事件處理

     • 訊息處理

     • 介面佈局

     • 介面繪製

     • 介面重新整理

除此之外,應該儘量避免將其他處理放在主執行緒中,特別複雜的資料計算和網路請求等。

1.佈局優化

佈局是否合理主要影響的是頁面測量時間的多少,我們知道一個頁面的顯示測量和繪製過程都是通過遞迴來完成的,多叉樹遍歷的時間與樹的高度h有關,其時間複雜度 O(h),如果層級太深,每增加一層則會增加更多的頁面顯示時間,所以佈局的合理性就顯得很重要。

那佈局優化有哪些方法呢,主要通過減少層級、減少測量和繪製時間、提高複用性三個方面入手。總結如下:

     • 減少層級。合理使用 RelativeLayout 和 LinerLayout,合理使用Merge。

     • 提高顯示速度。使用 ViewStub,它是一個看不見的、不佔佈局位置、佔用資源非常小的檢視物件。

     • 佈局複用。可以通過 標籤來提高複用。

     • 儘可能少用wrap_content。wrap_content 會增加布局 measure 時計算成本,在已知寬高為固定值時,不用wrap_content 。

     • 刪除控制元件中無用的屬性。

2,避免過度繪製

過度繪製是指在螢幕上的某個畫素在同一幀的時間內被繪製了多次。在多層次重疊的 UI 結構中,如果不可見的 UI 也在做繪製的操作,就會導致某些畫素區域被繪製了多次,從而浪費了多餘的 CPU 以及 GPU 資源。

如何避免過度繪製呢,如下:

     • 佈局上的優化。移除 XML 中非必須的背景,移除 Window 預設的背景、按需顯示佔位背景圖片

     • 自定義View優化。使用 canvas.clipRect()來幫助系統識別那些可見的區域,只有在這個區域內才會被繪製。

3,啟動優化

通過對啟動速度的監控,發現影響啟動速度的問題所在,優化啟動邏輯,提高應用的啟動速度。啟動主要完成三件事:UI 佈局、繪製和資料準備。因此啟動速度優化就是需要優化這三個過程:

     • UI 佈局。應用一般都有閃屏頁,優化閃屏頁的 UI 佈局,可以通過 Profile GPU Rendering 檢測丟幀情況。

     • 啟動載入邏輯優化。可以採用分佈載入、非同步載入、延期載入策略來提高應用啟動速度。

     • 資料準備。資料初始化分析,載入資料可以考慮用執行緒初始化等策略。

4,合理的重新整理機制

在應用開發過程中,因為資料的變化,需要重新整理頁面來展示新的資料,但頻繁重新整理會增加資源開銷,並且可能導致卡頓發生,因此,需要一個合理的重新整理機制來提高整體的 UI 流暢度。合理的重新整理需要注意以下幾點:

     • 儘量減少重新整理次數。

     • 儘量避免後臺有高的 CPU 執行緒執行。

     • 縮小重新整理區域。

5,其他

在實現動畫效果時,需要根據不同場景選擇合適的動畫框架來實現。有些情況下,可以用硬體加速方式來提供流暢度。

記憶體優化

一、 Android的記憶體機制

    Android的程式由Java語言編寫,所以Android的記憶體管理與Java的記憶體管理相似。程式設計師通過new為物件分配記憶體,所有物件在java堆內分配空間;然而物件的釋放是由垃圾回收器來完成的。C/C++中的記憶體機制是“誰汙染,誰治理”,java的就比較人性化了,給我們請了一個專門的清潔工(GC)。

二、Android的記憶體溢位

 Android的記憶體溢位是如何發生的?

 Android的虛擬機器是基於暫存器的Dalvik,它的最大堆大小一般是16M,有的機器為24M。因此我們所能利用的記憶體空間是有限的。如果我們的記憶體佔用超過了一定的水平就會出現OutOfMemory的錯誤。

為什麼會出現記憶體不夠用的情況呢?我想原因主要有兩個:

     • 由於我們程式的失誤,長期保持某些資源(如Context)的引用,造成記憶體洩露,資源造成得不到釋放。

     • 儲存了多個耗用記憶體過大的物件(如Bitmap),造成記憶體超出限制。

常見記憶體洩漏場景

如果在記憶體洩漏發生後再去找原因並修復會增加開發的成本,最好在編寫程式碼時就能夠很好地考慮記憶體問題,寫出更高質量的程式碼,這裡列出一些常見的記憶體洩漏場景,在以後的開發過程中需要避免這類問題。

     • 資源性物件未關閉。比如Cursor、File檔案等,往往都用了一些緩衝,在不使用時,應該及時關閉它們。

     • 註冊物件未登出。比如事件註冊後未登出,會導致觀察者列表中維持著物件的引用。

     • 類的靜態變數持有大資料物件。

     • 非靜態內部類的靜態例項。

     • Handler臨時性記憶體洩漏。如果Handler是非靜態的,容易導致 Activity 或 Service 不會被回收。

     • 容器中的物件沒清理造成的記憶體洩漏。

     • WebView。WebView 存在著記憶體洩漏的問題,在應用中只要使用一次 WebView,記憶體就不會被釋放掉。

優化記憶體空間

沒有記憶體洩漏,並不意味著記憶體就不需要優化,在移動裝置上,由於物理裝置的儲存空間有限,Android 系統對每個應用程式也都分配了有限的堆記憶體,因此使用最小記憶體物件或者資源可以減小記憶體開銷,同時讓GC 能更高效地回收不再需要使用的物件,讓應用堆記憶體保持充足的可用記憶體,使應用更穩定高效地執行。常見做法如下:

     • 物件引用。強引用、軟引用、弱引用、虛引用四種引用型別,根據業務需求合理使用不同,選擇不同的引用型別。

     • 減少不必要的記憶體開銷。注意自動裝箱,增加記憶體複用,比如有效利用系統自帶的資源、檢視複用、物件池、Bitmap物件的複用。

     • 使用最優的資料型別。比如針對資料類容器結構,可以使用ArrayMap資料結構,避免使用列舉型別,使用快取Lrucache等等。

     • 圖片記憶體優化。可以設定點陣圖規格,根據取樣因子做壓縮,用一些圖片快取方式對圖片進行管理等等。

穩定性優化

Android 應用的穩定性定義很寬泛,影響穩定性的原因很多,比如記憶體使用不合理、程式碼異常場景考慮不周全、程式碼邏輯不合理等,都會對應用的穩定性造成影響。其中最常見的兩個場景是:Crash 和 ANR,這兩個錯誤將會使得程式無法使用,比較常用的解決方式如下:

     • 提高程式碼質量。比如開發期間的程式碼稽核,看些程式碼設計邏輯,業務合理性等。

     • 程式碼靜態掃描工具。常見工具有Android Lint、Findbugs、Checkstyle、PMD等等。

     • Crash監控。把一些崩潰的資訊,異常資訊及時地記錄下來,以便後續分析解決。

     • Crash上傳機制。在Crash後,儘量先儲存日誌到本地,然後等下一次網路正常時再上傳日誌資訊。

耗電優化

在移動裝置中,電池的重要性不言而喻,沒有電什麼都幹不成。對於作業系統和裝置開發商來說,耗電優化一致沒有停止,去追求更長的待機時間,而對於一款應用來說,並不是可以忽略電量使用問題,特別是那些被歸為“電池殺手”的應用,最終的結果是被解除安裝。因此,應用開發者在實現需求的同時,需要儘量減少電量的消耗。

在 Android5.0 以前,在應用中測試電量消耗比較麻煩,也不準確,5.0 之後專門引入了一個獲取裝置上電量消耗資訊的 API:Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,和Systrace 一樣,是一款圖形化資料分析工具,直觀地展示出手機的電量消耗過程,通過輸入電量分析檔案,顯示消耗情況,最後提供一些可供參考電量優化的方法。

除此之外,還有一些常用方案可提供:

     • 計算優化,避開浮點運算等。

     • 避免 WaleLock 使用不當。

     • 使用 Job Scheduler。

小結

效能優化不是更新一兩個版本就可以解決的,是持續性的需求,持續整合迭代反饋。在實際的專案中,在專案剛開始的時候,由於人力和專案完成時間限制,效能優化的優先順序比較低,等進入專案投入使用階段,就需要把優先順序提高,但在專案初期,在設計架構方案時,效能優化的點也需要提早考慮進去,這就體現出一個程式設計師的技術功底了。

什麼時候開始有效能優化的需求,往往都是從發現問題開始,然後分析問題原因及背景,進而尋找最優解決方案,最終解決問題,這也是日常工作中常會用到的處理方式。

原文釋出時間為:2018-09-11

本文來自雲棲社群合作伙伴“Android開發中文站”,瞭解相關資訊可以關注“Android開發中文站”。


相關文章