1. 程式人生 > >App效能優化(2)

App效能優化(2)

在網上也看到過很多相關的文章,他們基本總結為:快,穩,省,小,描述的很準確.如下圖

如何讓 app 在執行過程過不卡頓,執行流暢,速度快,也就是說如何解決卡頓呢?我們先看看那些因素影響卡頓? 

  1. UI,包括ui的繪製,重新整理等 

  2. 啟動,包括冷啟動,熱啟動,溫啟動等 

  3. 跳轉,頁面跳轉,前後天切換 

  4. 及時反饋,點選事件,滑動,系統事件

UI

這個涉及到 android 的系統顯示原理,我們簡單瞭解一下:

Android 顯示過程可以簡單概括為:Android 應用程式把經過測量,佈局、繪製後的 surface 快取資料,通過 SurfaceFlinger 把資料渲染到顯示螢幕上, 通過 Android 的重新整理機制來重新整理資料。也就是說應用層負責繪製,系統層負責渲染,通過程序間通訊把應用層需要繪製的資料傳遞到系統層服務,系統層服務通過重新整理機制把資料更新到螢幕上。

換一種方式說:Android 系統每隔 16ms 發出 VSYNC 訊號,觸發對 UI 進行渲染,如果每次渲染都成功,這樣就能夠達到流暢的畫面所需的 60FPS。(注:FPS 表示每秒傳遞的幀數。)在理想情況下,60 FPS 就感覺不到卡,這意味著每個繪製時長應該在16 ms 左右。如果某個操作花費的時間是 24ms ,系統在得到 VSYNC 訊號時就無法正常進行正常渲染,這樣就發生了丟幀現象。也就是延遲了,這種現象在執行動畫或滑動列表比較常見,還有可能是你的 Layout 太過複雜,層疊太多的繪製單元,無法在 16ms 完成渲染,最終引起重新整理不及時.

那麼我們如何解決呢,主要從兩點入手:ui佈局,繪製優化和主執行緒優化?

佈局優化

  • 避免ui佈局優化可以先從合理使用背景色開始,比如:如果子view和父佈局公用一個背景色就沒有必要了。

  • 減少不必要的巢狀,一般建議不超過5層

  • 合理使用各種佈局,儘量使用 LinearLayout 和 FrameLayout,因為 RelativeLayout 需要比較複雜,測繪也比較費時,強調一下這個是相對的,不是說 LinearLayout 一定比 RelativeLayout 好。

  • 合理使用 include、merge 和 ViewStub,使用include和merge增加複用,減少層級; ViewStub 按需載入。

  • 推薦使用 google 已經出來的新的佈局 ConstraintLayout,這個有機會說。

繪製優化

我們之前說過根據 Android 系統顯示的原理,View 的繪製頻率保證 60fps 是最佳的,這就要求每幀繪製時間不超過16ms(16ms = 1000/60),因此要減輕 onDraw() 的負擔。所以在繪製時要注意兩點:

  1. onDraw 中不要建立新的區域性物件。

  2. onDraw 方法中不要做耗時的任務。

還有就是重新整理,重新整理的話儘量減少不必要的重新整理和儘可能減少重新整理面積

啟動優化

  • 冷啟動

冷啟動是指安裝 apk 後首次啟動應用程式,或者應用程式上次結束,程序被殺死後重新開啟app.

在冷啟動開始時,系統有三個任務。這些任務是:

  1. 載入並啟動應用程式

  2. 啟動後立即顯示應用程式的空白啟動視窗

  3. 建立應用程式程序

當系統為我們建立了應用程序之後,會執行以下的操作:

  • application 的初始化

  • 啟動 UI 執行緒

  • 建立 Activity

  • 匯入檢視(inflate view)

  • 計算檢視大小(onmesure view)

  • 得到檢視排版(onlayout view)

  • 繪製檢視(ondraw view)

應用程式程序完成首次繪製後,系統程序會交換當前顯示的背景視窗,將其替換為主活動。此時至此啟動完成,使用者可以使用程式(app)了,那麼這裡就會有兩類建立:

  • Application 的建立

    當 Application 啟動時,會有一個空白的啟動視窗保留在螢幕上,直到系統首次完成繪製應用程式,白屏才會消失,這也是為什麼啟動app會出現白屏,這個問題,我也有提到過解決方式 Anroid 白屏

  • Activity的建立

    當 Application 首次啟動完成繪製後,我們的 UI 執行緒會執行主活動進行以下操作:

    • 初始化值。

    • 執行其建構函式。

    • 執行其回撥方法,比如 Activity 的 onCreate()對應生命週期的狀態,onCreate() 方法做的事情越多,冷啟動消耗的時間越長。

  • 暖(溫)啟動

暖啟動比冷啟動時間更短。在暖啟動中,系統都會把你的 Activity 帶到前臺。如果應用程式的 Activity 仍然駐留在記憶體中,那麼應用程式可以避免重複物件初始化、佈局載入和渲染,但系統依然會展示閃屏頁,直到第一個 Activity 的內容呈現為止。比如:當應用中的 Activities 被銷燬,但在記憶體中常駐時,應用的啟動方式就會變為暖啟動 。

  • 熱啟動

熱啟動的啟動時間比暖啟動還要更短。你比如,我使用者 Back 退出應用程式,然後又重新啟動,應用程式會再次執行 Activity 的 onCreate(),但會從 Bundle(savedInstanceState)獲取資料,我們平時應用成勳崩潰,不也是通過該方法儲存資料的嗎。

針對啟動方式的優化

Application 的建立過程中儘量少的進行耗時操作。比如:

Application 的 onCreate() 中進行友盟,bugly, okhttp,地圖,推送等 init() 等操作。如果是必須在 onCreate 中進行的如:okhttp 等網路請求框架我們在 onCreate 中進行,其他的友盟,百度地圖啥的我們可以等程式起來後再 onResume 方法中執行,bugly 等 sdk 可以非同步載入。

在生命週期回撥的方法中儘量減少耗時的操作

這個裡面的優化方式就是:避免 I/O 操作、反序列化、網路操作、佈局巢狀等。

主執行緒優化

主執行緒的優化大部分是指記憶體優化,不要記憶體洩漏,那麼通常那些地方容易引起記憶體洩漏呢?

  • 集合類洩漏

  • 單例/靜態變數造成的記憶體洩漏

  • 匿名內部類/非靜態內部類

  • 資源未關閉造成的記憶體洩漏

解決方式

比如我們的List集合add()元素之後,會引用著集合元素物件,導致該集合中的元素物件無法被回收,從而導致記憶體洩露。當我們的List集合沒有用的時候,一定要

  list.clear()
  list=null

針對單例引起的記憶體洩漏,通常是由於引用的context是生命週期短造成的,也就是說生命週期長的持有了生命週期短的引用,造成了記憶體洩漏。比如Toast,我們傳入的是MainActivity,但MainActivity沒有用了,需要被銷燬,但我們的Tost依然持有其引用導致無法回收,這就導致了記憶體洩漏。

匿名內部類或非靜態內部類導致的記憶體洩漏,這個我們可以採用合理使用JAVA的引用機制來解決,我上一篇文章有詳解,參考Android-強,軟,弱,虛引用.

資源未關閉導致的記憶體洩漏就比較好說了,我們平時要多檢查,用完後及時關閉無用資源:

  • 網路、檔案等流忘記關閉

  • 手動註冊廣播時,退出時忘記 unregisterReceiver()

  • Service 執行完後忘記 stopSelf()

  • EventBus 等觀察者模式的框架忘記手動解除註冊

  • 注意 Bitmap,用完及時 Recycle()

小大多指應用程式apk體積要小。我們先看看一個apk檔案有哪些解壓後有哪些檔案:

  • 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 尋找資源。

通常我減小 apk 體積的方式都是:先用 studio 自帶的程式碼掃描分析工具 lint 刪除無用資源;開啟混淆,設定   shrinkResources true和 minifyEnabled true;當然你也可以藉助第三方工具如 :樂固加固,360壓縮啥的;還有注意不要重複使用庫;外掛化,比如功能模組放在伺服器上,按需下載,可以減少安裝包大小等都是常見的減少 apk 體積的方式。

省電

谷歌推薦使用 JobScheduler,來調整任務優先順序等策略來達到降低損耗的目的。JobScheduler 可以避免頻繁的喚醒硬體模組,造成不必要的電量消耗。避免在不合適的時間(例如低電量情況下、弱網路或者行動網路情況下的)執行過多的任務消耗電量。這個我們以後說。

省記憶體

主要是載入圖片,動不動就 OOM,對於圖片的壓縮無非是:

  • 圖片尺寸壓縮

  • 圖片質量壓縮

此處程式碼省略,網上一大堆。Glide就是採用了 Lrucache 和 LruDiskCache 推薦使用。

省 cpu 資源

比如:執行緒的使用,這裡我推薦使用執行緒池,我也寫過相關文章,感興趣的可以瞭解一下。Android-ThreadPooll.:

https://www.jianshu.com/p/07eb2f7db0ee

其他

這都是本人的一些建議:

  • 序列化採用推薦的 Parcelable 代替 Serializable

  • 集合如果是插入和刪除用的多,建議使用 LinkList。如果修改用的多,建議 ArrayList。

  • 寫程式要思考,避免建立不必要的物件。

  • 對常量使用 static final,適用於基本型別和 String 常量。

  • 使用增強的 for 迴圈語法(foreach)。

  • 避免使用浮點數,浮點數比 Android 裝置上的整數慢約2倍。

  • 儘可能少用 wrap_content,wrap_content 會增加布局 measure 時計算成本。

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

  • 合理使用動畫,某些情況下可以用硬體加速方式來提供流暢度,或者採用自定義view代替動畫,最後記得在Activity的ondestory()方法中呼叫Animation.cancle()進行動畫停止。

  • 注意 webview 和 handler,一般在首次載入後 webview 就會存在於記憶體中,容易記憶體洩漏。

  • 考慮 StringBuilder 代替 String

  • 資料量比較大或者記憶體比較寬裕考慮 HashMap,其他建議使用 SpareArray

最後,我們一定要學會使用 Android Studio 自帶的各種工具如:

  • Lint:提示未使用到資源,不規範的程式碼,優化建議等。

    使用:選擇 Analyze > Inspect Code 具體百度

  • 使用 Android Profiler 檢視記憶體,已經各個操作記憶體和網路的變化。

  • 藉助第三方工具,這個就多了去了,比如 LeakCanary,MemoryAnalyzer 等