說到 Android 系統手機,大部分人的印象是用了一段時間就變得有點卡頓,有些程序在運行期間莫名其妙的出現崩潰,打開系統文件夾一看,發現多了不少文件,而後用手機管家 APP 不斷地進行清理優化 ,才感受運行速度稍微提升了點,就算手機在各類性能跑分軟件面前分數遙遙領先,仍是感受不管有多大的內存空間都遠遠不夠用。相信每一個使用 Android 系統的用戶都有過以上相似經歷,確實,Android 系統在流暢性方面不如 IOS 系統,爲什麼呢,明明在看手機硬件配置上時,Android 設備都不會輸於 IOS 設備,甚至都強於它,關鍵是在於軟件上。形成這種現象的緣由是多方面的,簡單羅列幾點以下:程序員
今天想說的重點是Android APP 性能優化,也就是在開發應用程序時應該注意的點有哪些,如何更好地提升用戶體驗。一個好的應用,除了要有吸引人的功能和交互以外,在性能上也應該有高的要求,即時應用很是具備特點,在產品前期可能吸引了部分用戶,可是用戶體驗很差的話,也會給產品帶來很差的口碑。那麼一個好的應用應該如何定義呢?主要有如下三方面:canvas
衆所周知,Android 系統做爲以移動設備爲主的操做系統,硬件配置是有必定的限制的,雖然配置如今愈來愈高級,但仍然沒法與 PC 相比,在 CPU 和內存上使用不合理或者耗費資源多時,就會碰到內存不足致使的穩定性問題、CPU 消耗太多致使的卡頓問題等。緩存
面對問題時,你們想到的都是聯繫用戶,而後查看日誌,但卻不知有關性能類問題的反饋,緣由也很是難找,日誌大多用處不大,爲什麼呢?由於性能問題大部分是非必現的問題,問題定位很難復現,而又沒有關鍵的日誌,固然就沒法找到緣由了。這些問題很是影響用戶體驗和功能使用,因此瞭解一些性能優化的一些解決方案就顯得很重要了,並在實際的項目中優化咱們的應用,進而提升用戶體驗。性能優化
能夠把用戶體驗的性能問題主要總結爲4個類別:服務器
性能問題的主要緣由是什麼,緣由有相同的,也有不一樣的,但歸根到底,不外乎內存使用、代碼效率、合適的策略邏輯、代碼質量、安裝包體積這一類問題,整理歸類以下:網絡
從圖中能夠看到,打造一個高質量的應用應該以4個方向爲目標:快、穩、省、小。數據結構
快:使用時避免出現卡頓,響應速度快,減小用戶等待的時間,知足用戶指望。架構
穩:減低 crash 率和 ANR 率,不要在用戶使用過程當中崩潰和無響應。框架
省:節省流量和耗電,減小用戶使用成本,避免使用時致使手機發燙。異步
小:安裝包小能夠下降用戶的安裝成本。
要想達到這4個目標,具體實現是在右邊框裏的問題:卡頓、內存使用不合理、代碼質量差、代碼邏輯亂、安裝包過大,這些問題也是在開發過程當中碰到最多的問題,在實現業務需求同時,也須要考慮到這點,多花時間去思考,如何避免功能完成後再來作優化,否則的話等功能實現後帶來的維護成本會增長。
Android 應用啓動慢,使用時常常卡頓,是很是影響用戶體驗的,應該儘可能避免出現。卡頓的場景有不少,按場景能夠分爲4類:UI 繪製、應用啓動、頁面跳轉、事件響應,如圖:
這4種卡頓場景的根本緣由能夠分爲兩大類:
引發卡頓的緣由不少,但無論怎麼樣的緣由和場景,最終都是經過設備屏幕上顯示來達到用戶,歸根到底就是顯示有問題,因此,要解決卡頓,就要先了解 Android 系統的顯示原理。
Android 顯示過程能夠簡單歸納爲:Android 應用程序把通過測量、佈局、繪製後的 surface 緩存數據,經過 SurfaceFlinger 把數據渲染到顯示屏幕上, 經過 Android 的刷新機制來刷新數據。也就是說應用層負責繪製,系統層負責渲染,經過進程間通訊把應用層須要繪製的數據傳遞到系統層服務,系統層服務經過刷新機制把數據更新到屏幕上。
咱們都知道在 Android 的每一個 View 繪製中有三個核心步驟:Measure、Layout、Draw。具體實現是從 ViewRootImp 類的performTraversals() 方法開始執行,Measure 和 Layout都是經過遞歸來獲取 View 的大小和位置,而且以深度做爲優先級,能夠看出層級越深、元素越多、耗時也就越長。
真正把須要顯示的數據渲染到屏幕上,是經過系統級進程中的 SurfaceFlinger 服務來實現的,那麼這個SurfaceFlinger 服務主要作了哪些工做呢?以下:
既然是兩個不一樣的進程,那麼確定是須要一個跨進程的通訊機制來實現數據傳遞,在 Android 顯示系統中,使用了 Android 的匿名共享內存:SharedClient,每個應用和 SurfaceFlinger 之間都會建立一個SharedClient ,而後在每一個 SharedClient 中,最多能夠建立 31 個 SharedBufferStack,每一個 Surface 都對應一個 SharedBufferStack,也就是一個 Window。
一個 SharedClient 對應一個Android 應用程序,而一個 Android 應用程序可能包含多個窗口,即 Surface 。也就是說 SharedClient 包含的是 SharedBufferStack的集合,其中在顯示刷新機制中用到了雙緩衝和三重緩衝技術。最後總結起來顯示總體流程分爲三個模塊:應用層繪製到緩存區,SurfaceFlinger 把緩存區數據渲染到屏幕,因爲是不一樣的進程,因此使用 Android 的匿名共享內存 SharedClient 緩存須要顯示的數據來達到目的。
除此以外,咱們還須要一個名詞:FPS。FPS 表示每秒傳遞的幀數。在理想狀況下,60 FPS 就感受不到卡,這意味着每一個繪製時長應該在16 ms 之內。可是 Android 系統頗有可能沒法及時完成那些複雜的頁面渲染操做。Android 系統每隔 16ms 發出 VSYNC 信號,觸發對 UI 進行渲染,若是每次渲染都成功,這樣就可以達到流暢的畫面所需的 60FPS。若是某個操做花費的時間是 24ms ,系統在獲得 VSYNC 信號時就沒法正常進行正常渲染,這樣就發生了丟幀現象。那麼用戶在 32ms 內看到的會是同一幀畫面,這種現象在執行動畫或滑動列表比較常見,還有多是你的 Layout 太過複雜,層疊太多的繪製單元,沒法在 16ms 完成渲染,最終引發刷新不及時。
根據Android 系統顯示原理能夠看到,影響繪製的根本緣由有如下兩個方面:
繪製耗時太長,有一些工具能夠幫助咱們定位問題。主線程太忙則須要注意了,主線程關鍵職責是處理用戶交互,在屏幕上繪製像素,並進行加載顯示相關的數據,因此特別須要避免任何主線程的事情,這樣應用程序才能保持對用戶操做的即時響應。總結起來,主線程主要作如下幾個方面工做:
除此以外,應該儘可能避免將其餘處理放在主線程中,特別複雜的數據計算和網絡請求等。
性能問題並不容易復現,也很差定位,可是真的碰到問題仍是須要去解決的,那麼分析問題和確認問題是否解決,就須要藉助相應的的調試工具,好比查看 Layout 層次的 Hierarchy View、Android 系統上帶的 GPU Profile 工具和靜態代碼檢查工具 Lint 等,這些工具對性能優化起到很是重要的做用,因此要熟悉,知道在什麼場景用什麼工具來分析。
1,Profile GPU Rendering
在手機開發者模式下,有一個卡頓檢測工具叫作:Profile GPU Rendering,如圖:
它的功能特色以下:
2,TraceView
TraceView 是 Android SDK 自帶的工具,用來分析函數調用過程,能夠對 Android 的應用程序以及 Framework 層的代碼進行性能分析。它是一個圖形化的工具,最終會產生一個圖表,用於對性能分析進行說明,能夠分析到每個方法的執行時間,其中能夠統計出該方法調用次數和遞歸次數,實際時長等參數維度,使用很是直觀,分析性能很是方便。
3,Systrace UI 性能分析
Systrace 是 Android 4.1及以上版本提供的性能數據採樣和分析工具,它是經過系統的角度來返回一些信息。它能夠幫助開發者收集 Android 關鍵子系統,如 surfaceflinger、WindowManagerService 等 Framework 部分關鍵模塊、服務、View系統等運行信息,從而幫助開發者更直觀地分析系統瓶頸,改進性能。Systrace 的功能包括跟蹤系統的 I/O 操做、內核工做隊列、CPU 負載等,在 UI 顯示性能分析上提供很好的數據,特別是在動畫播放不流暢、渲染卡等問題上。
1,佈局優化
佈局是否合理主要影響的是頁面測量時間的多少,咱們知道一個頁面的顯示測量和繪製過程都是經過遞歸來完成的,多叉樹遍歷的時間與樹的高度h有關,其時間複雜度 O(h),若是層級太深,每增長一層則會增長更多的頁面顯示時間,因此佈局的合理性就顯得很重要。
那佈局優化有哪些方法呢,主要經過減小層級、減小測量和繪製時間、提升複用性三個方面入手。總結以下:
2,避免過分繪製
過分繪製是指在屏幕上的某個像素在同一幀的時間內被繪製了屢次。在多層次重疊的 UI 結構中,若是不可見的 UI 也在作繪製的操做,就會致使某些像素區域被繪製了屢次,從而浪費了多餘的 CPU 以及 GPU 資源。
如何避免過分繪製呢,以下:
3,啓動優化
經過對啓動速度的監控,發現影響啓動速度的問題所在,優化啓動邏輯,提升應用的啓動速度。啓動主要完成三件事:UI 佈局、繪製和數據準備。所以啓動速度優化就是須要優化這三個過程:
4,合理的刷新機制
在應用開發過程當中,由於數據的變化,須要刷新頁面來展現新的數據,但頻繁刷新會增長資源開銷,而且可能致使卡頓發生,所以,須要一個合理的刷新機制來提升總體的 UI 流暢度。合理的刷新須要注意如下幾點:
5,其餘
在實現動畫效果時,須要根據不一樣場景選擇合適的動畫框架來實現。有些狀況下,能夠用硬件加速方式來提供流暢度。
在 Android 系統中有個垃圾內存回收機制,在虛擬機層自動分配和釋放內存,所以不須要在代碼中分配和釋放某一塊內存,從應用層面上不容易出現內存泄漏和內存溢出等問題,可是須要內存管理。Android 系統在內存管理上有一個 Generational Heap Memory 模型,內存回收的大部分壓力不須要應用層關心, Generational Heap Memory 有本身一套管理機制,當內存達到一個閾值時,系統會根據不一樣的規則自動釋放系統認爲能夠釋放的內存,也正是由於 Android 程序把內存控制的權力交給了 Generational Heap Memory,一旦出現內存泄漏和溢出方面的問題,排查錯誤將會成爲一項異常艱難的工做。除此以外,部分 Android 應用開發人員在開發過程當中並無特別關注內存的合理使用,也沒有在內存方面作太多的優化,當應用程序同時運行愈來愈多的任務,加上愈來愈複雜的業務需求時,徹底依賴 Android 的內存管理機制就會致使一系列性能問題逐漸呈現,對應用的穩定性和性能帶來不可忽視的影響,所以,解決內存問題和合理優化內存是很是有必要的。
Android 應用都是在 Android 的虛擬機上運行,應用 程序的內存分配與垃圾回收都是由虛擬機完成的。在 Android 系統,虛擬機有兩種運行模式:Dalvik 和 ART。
1,Java對象生命週期
通常Java對象在虛擬機上有7個運行階段:
建立階段->應用階段->不可見階段->不可達階段->收集階段->終結階段->對象空間從新分配階段
2,內存分配
在 Android 系統中,內存分配其實是對堆的分配和釋放。當一個 Android 程序啓動,應用進程都是從一個叫作 Zygote 的進程衍生出來,系統啓動 Zygote 進程後,爲了啓動一個新的應用程序進程,系統會衍生 Zygote 進程生成一個新的進程,而後在新的進程中加載並運行應用程序的代碼。其中,大多數的 RAM pages 被用來分配給Framework 代碼,同時促使 RAM 資源可以在應用全部進程之間共享。
可是爲了整個系統的內存控制須要,Android 系統會爲每個應用程序都設置一個硬性的 Dalvik Heap Size 最大限制閾值,整個閾值在不一樣設備上會由於 RAM 大小不一樣而有所差別。若是應用佔用內存空間已經接近整個閾值時,再嘗試分配內存的話,就很容易引發內存溢出的錯誤。
3,內存回收機制
咱們須要知道的是,在 Java 中內存被分爲三個區域:Young Generation(年輕代)、Old Generation(年老代)、Permanent Generation(持久代)。最近分配的對象會存放在 Young Generation 區域。對象在某個時機觸發 GC 回收垃圾,而沒有回收的就根據不一樣規則,有可能被移動到 Old Generation,最後累積必定時間在移動到 Permanent Generation 區域。系統會根據內存中不一樣的內存數據類型分別執行不一樣的 GC 操做。GC 經過肯定對象是否被活動對象引用來肯定是否收集對象,進而動態回收無任何引用的對象佔據的內存空間。但須要注意的是頻繁的 GC 會增長應用的卡頓狀況,影響應用的流暢性,所以須要儘可能減小系統 GC 行爲,以便提升應用的流暢度,減少卡頓發生的機率。
作內存優化前,須要瞭解當前應用的內存使用現狀,經過現狀去分析哪些數據類型有問題,各類類型的分佈狀況如何,以及在發現問題後如何發現是哪些具體對象致使的,這就須要相關工具來幫助咱們。
1,Memory Monitor
Memory Monitor 是一款使用很是簡單的圖形化工具,能夠很好地監控系統或應用的內存使用狀況,主要有如下功能:
2,Heap Viewer
Heap Viewer 的主要功能是查看不一樣數據類型在內存中的使用狀況,能夠看到當前進程中的 Heap Size 的狀況,分別有哪些類型的數據,以及各類類型數據佔比狀況。經過分析這些數據來找到大的內存對象,再進一步分析這些大對象,進而經過優化減小內存開銷,也能夠經過數據的變化發現內存泄漏。
3,Allocation Tracker
Memory Monitor 和 Heap Viewer 均可以很直觀且實時地監控內存使用狀況,還能發現內存問題,但發現內存問題後不能再進一步找到緣由,或者發現一塊異常內存,但不能區別是否正常,同時在發現問題後,也不能定位到具體的類和方法。這時就須要使用另外一個內存分析工具 Allocation Tracker,進行更詳細的分析, Allocation Tracker 能夠分配跟蹤記錄應用程序的內存分配,並列出了它們的調用堆棧,能夠查看全部對象內存分配的週期。
4,Memory Analyzer Tool(MAT)
MAT 是一個快速,功能豐富的 Java Heap 分析工具,經過分析 Java 進程的內存快照 HPROF 分析,從衆多的對象中分析,快速計算出在內存中對象佔用的大小,查看哪些對象不能被垃圾收集器回收,並能夠經過視圖直觀地查看可能形成這種結果的對象。
若是在內存泄漏發生後再去找緣由並修復會增長開發的成本,最好在編寫代碼時就可以很好地考慮內存問題,寫出更高質量的代碼,這裏列出一些常見的內存泄漏場景,在之後的開發過程當中須要避免這類問題。
除此以外,內存泄漏可監控,常見的就是用LeakCanary 第三方庫,這是一個檢測內存泄漏的開源庫,使用很是簡單,能夠在發生內存泄漏時告警,而且生成 leak tarce 分析泄漏位置,同時能夠提供 Dump 文件進行分析。
沒有內存泄漏,並不意味着內存就不須要優化,在移動設備上,因爲物理設備的存儲空間有限,Android 系統對每一個應用進程也都分配了有限的堆內存,所以使用最小內存對象或者資源能夠減少內存開銷,同時讓GC 能更高效地回收再也不須要使用的對象,讓應用堆內存保持充足的可用內存,使應用更穩定高效地運行。常見作法以下:
Android 應用的穩定性定義很寬泛,影響穩定性的緣由不少,好比內存使用不合理、代碼異常場景考慮不周全、代碼邏輯不合理等,都會對應用的穩定性形成影響。其中最多見的兩個場景是:Crash 和 ANR,這兩個錯誤將會使得程序沒法使用,比較經常使用的解決方式以下:
在移動設備中,電池的重要性不言而喻,沒有電什麼都幹不成。對於操做系統和設備開發商來講,耗電優化一致沒有中止,去追求更長的待機時間,而對於一款應用來講,並非能夠忽略電量使用問題,特別是那些被歸爲「電池殺手」的應用,最終的結果是被卸載。所以,應用開發者在實現需求的同時,須要儘可能減小電量的消耗。
在 Android5.0 之前,在應用中測試電量消耗比較麻煩,也不許確,5.0 以後專門引入了一個獲取設備上電量消耗信息的 API:Battery Historian。Battery Historian 是一款由 Google 提供的 Android 系統電量分析工具,和Systrace 同樣,是一款圖形化數據分析工具,直觀地展現出手機的電量消耗過程,經過輸入電量分析文件,顯示消耗狀況,最後提供一些可供參考電量優化的方法。
除此以外,還有一些經常使用方案可提供:
應用安裝包大小對應用使用沒有影響,但應用的安裝包越大,用戶下載的門檻越高,特別是在移動網絡狀況下,用戶在下載應用時,對安裝包大小的要求更高,所以,減少安裝包大小可讓更多用戶願意下載和體驗產品。
經常使用應用安裝包的構成,如圖所示:
從圖中咱們能夠看到:
assets文件夾。存放一些配置文件、資源文件,assets不會自動生成對應的 ID,而是經過 AssetManager 類的接口獲取。
res。res 是 resource 的縮寫,這個目錄存放資源文件,會自動生成對應的 ID 並映射到 .R 文件中,訪問直接使用資源 ID。
META-INF。保存應用的簽名信息,簽名信息能夠驗證 APK 文件的完整性。
AndroidManifest.xml。這個文件用來描述 Android 應用的配置信息,一些組件的註冊信息、可以使用權限等。
classes.dex。Dalvik 字節碼程序,讓 Dalvik 虛擬機可執行,通常狀況下,Android 應用在打包時經過 Android SDK 中的 dx 工具將 Java 字節碼轉換爲 Dalvik 字節碼。
resources.arsc。記錄着資源文件和資源 ID 之間的映射關係,用來根據資源 ID 尋找資源。
減小安裝包大小的經常使用方案
性能優化不是更新一兩個版本就能夠解決的,是持續性的需求,持續集成迭代反饋。在實際的項目中,在項目剛開始的時候,因爲人力和項目完成時間限制,性能優化的優先級比較低,等進入項目投入使用階段,就須要把優先級提升,但在項目初期,在設計架構方案時,性能優化的點也須要提前考慮進去,這就體現出一個程序員的技術功底了。
何時開始有性能優化的需求,每每都是從發現問題開始,而後分析問題緣由及背景,進而尋找最優解決方案,最終解決問題,這也是平常工做中常會用到的處理方式。