Android客戶端性能優(yōu)化實(shí)踐

一、關(guān)于App性能優(yōu)化

1. 性能優(yōu)化分類(lèi)

Google官方給出的性能優(yōu)化教程,主要分為以下幾類(lèi):
1)布局與UI渲染優(yōu)化
2)運(yùn)算與內(nèi)存管理優(yōu)化
3)電池電量?jī)?yōu)化
4)高效代碼相關(guān)Tips
5)多線程操作

官方鏈接:https://developer.android.com/training/best-performance.html
中文博客:https://aeli.gitbooks.io/android-training-course/content/best-performance.html

2. 渲染性能與UI卡頓

大多數(shù)用戶感知到的卡頓等性能問(wèn)題的最主要根源都是因?yàn)殇秩拘阅?,Android系統(tǒng)很有可能無(wú)法及時(shí)完成那些復(fù)雜的界面渲染操作,Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染,如果每次渲染都成功,這樣就能夠達(dá)到流暢的畫(huà)面所需要的60fps,為了能夠?qū)崿F(xiàn)60fps,這意味著程序的大多數(shù)操作都必須在16ms內(nèi)完成。

圖1-1 Android界面渲染

如果你的某個(gè)操作花費(fèi)時(shí)間是24ms,系統(tǒng)在得到VSYNC信號(hào)的時(shí)候就無(wú)法進(jìn)行正常渲染,這樣就發(fā)生了丟幀現(xiàn)象。那么用戶在32ms內(nèi)看到的會(huì)是同一幀畫(huà)面。

圖1-2 Android渲染丟幀

用戶容易在UI執(zhí)行動(dòng)畫(huà)或者滑動(dòng)ListView的時(shí)候感知到卡頓不流暢,是因?yàn)檫@里的操作相對(duì)復(fù)雜,容易發(fā)生丟幀的現(xiàn)象,從而感覺(jué)卡頓。有很多原因可以導(dǎo)致丟幀,也許是因?yàn)槟愕膌ayout太過(guò)復(fù)雜,無(wú)法在16ms內(nèi)完成渲染,有可能是因?yàn)槟愕腢I上有層疊太多的繪制單元,還有可能是因?yàn)閯?dòng)畫(huà)執(zhí)行的次數(shù)過(guò)多。這些都會(huì)導(dǎo)致CPU或者GPU負(fù)載過(guò)重。

二、工具篇

我們可以通過(guò)一些工具來(lái)定位問(wèn)題,比如可以使用HierarchyViewer來(lái)查找Activity中的布局是否過(guò)于復(fù)雜,也可以使用手機(jī)設(shè)置里面的開(kāi)發(fā)者選項(xiàng),打開(kāi)Show GPU Overdraw等選項(xiàng)進(jìn)行觀察。你還可以使用TraceView來(lái)觀察CPU的執(zhí)行情況,更加快捷的找到性能瓶頸。

1. HierarchyViewer

HierarchyViewer是我們優(yōu)化程序的工具之一,它是Android自帶的非常有用的工具,可以幫助我們更好地檢測(cè)和設(shè)計(jì)用戶界面,能夠以可視化的角度直觀地獲得UI布局設(shè)計(jì)結(jié)構(gòu)和各種屬性的信息,幫助我們優(yōu)化布局設(shè)計(jì)。但是獨(dú)立的HierarchyViewer已廢棄,Android Studio中建議使用Android Device Monitor。
需使用模擬器或開(kāi)發(fā)版的真機(jī),因?yàn)樾枰到y(tǒng)中的Hierarchy Viewer服務(wù)開(kāi)啟。
使用方式:
1)點(diǎn)擊Tools->Android->Android Device Monitor,進(jìn)入Android Device Monitor,切換到HierarchyViewer標(biāo)簽。

圖2-1 Android HierarchyViewer標(biāo)簽

2)或者直接進(jìn)入獨(dú)立的HierarchyViewer:進(jìn)入sdk/tools路徑,找到HierarchyViewer,雙擊運(yùn)行。

圖2-2 運(yùn)行HierarchyViewer

3)在左側(cè)樹(shù)狀結(jié)構(gòu)中選中要查看的activity,加載完畢后會(huì)顯示當(dāng)前界面的樹(shù)狀結(jié)構(gòu)層次,從PhoneWindow.DecorView出發(fā),右側(cè)是xml內(nèi)定義的布局結(jié)構(gòu),大小可以縮放,可以拖動(dòng),右側(cè)是屬性預(yù)覽區(qū)域。

圖2-3 界面樹(shù)狀層次

4)觀察單個(gè)view,選擇單個(gè)view后會(huì)出現(xiàn)如下所示圖形,可以看到Measure、Layout、Draw的耗時(shí),從左到右三個(gè)圓點(diǎn),分別表示對(duì)Measure、Layout、Draw耗時(shí)性能的評(píng)級(jí),按照性能從高到低,分別為綠色、黃色和紅色,浮窗上是具體的數(shù)值

圖2-4 界面控制渲染耗時(shí)

通過(guò)使用HierarchyViewer,我們可以通過(guò)減少頁(yè)面層級(jí),優(yōu)化布局結(jié)構(gòu)來(lái)實(shí)現(xiàn)UI性能的優(yōu)化,針對(duì)每個(gè)單獨(dú)控件的渲染性能數(shù)據(jù),可以獨(dú)立的針對(duì)某控件做特殊優(yōu)化。

2. GPU過(guò)度繪制

過(guò)度繪制就是Overdraw,是指在一幀的時(shí)間內(nèi)(16.67ms)像素被繪制了多次,理論上一個(gè)像素每次只繪制一次是最優(yōu)的,但是由于重疊的布局導(dǎo)致一些像素會(huì)被多次繪制,而每次繪制都會(huì)對(duì)應(yīng)到CPU的一組繪圖命令和GPU的一些操作,產(chǎn)生額外開(kāi)銷(xiāo),當(dāng)這個(gè)操作耗時(shí)超過(guò)16.67ms時(shí),就會(huì)出現(xiàn)掉幀現(xiàn)象,也就是我們所說(shuō)的卡頓,所以應(yīng)盡量減少Overdraw的發(fā)生。
Android提供了測(cè)量Overdraw的選項(xiàng),在開(kāi)發(fā)者選項(xiàng)-調(diào)試GPU過(guò)度繪制(Show GPU Overdraw),打開(kāi)選項(xiàng)就可以看到當(dāng)前頁(yè)面Overdraw的狀態(tài),就可以觀察屏幕的繪制狀態(tài)。該工具會(huì)使用三種不同的顏色繪制屏幕,來(lái)指示overdraw發(fā)生在哪里以及程度如何,其中:

無(wú)顏色:沒(méi)有overdraw。像素只畫(huà)了一次。
藍(lán)色:overdraw 1倍。像素繪制了兩次。大片的藍(lán)色還是可以接受的(若整個(gè)窗口是藍(lán)色的,可以擺脫一層)。
綠色:overdraw 2倍。像素繪制了三次。中等大小的綠色區(qū)域是可以接受的但你應(yīng)該嘗試優(yōu)化、減少它們。
淺紅:overdraw 3倍。像素繪制了四次,小范圍可以接受。
暗紅:overdraw 4倍。像素繪制了五次或者更多。這是錯(cuò)誤的,要修復(fù)它們。

圖3-1 Android過(guò)度繪制

減少過(guò)度繪制Tips:

  • 不同控件容器特點(diǎn)不同,根據(jù)不同場(chǎng)景合理選擇控件容器,例如RelativeLayout
    相對(duì)于LinearLayout,在某些場(chǎng)景下可減少嵌套層次,但LinearLayout使用簡(jiǎn)單,效率也更高
  • 去掉window的默認(rèn)背景,DecorView會(huì)創(chuàng)建默認(rèn)背景,而自定義布局會(huì)蓋上一層背景圖或背景色,導(dǎo)致默認(rèn)背景無(wú)用,帶來(lái)繪制性能損耗
  • 去掉其他不必要的背景,減少背景重疊或覆蓋
  • 多使用輕量級(jí)的ViewStub(只有設(shè)置可見(jiàn)或inflate時(shí)才會(huì)實(shí)例化內(nèi)部的布局),避免將所有View寫(xiě)上,并動(dòng)態(tài)控制GONE、VISIBLE
  • 使用Merge,減少一層View層級(jí)
  • 慎用Alpha,對(duì)一個(gè)View做Alpha轉(zhuǎn)化,需要先將View繪制出來(lái),再做Alpha轉(zhuǎn)化,最后將轉(zhuǎn)換后效果繪制在界面上,也就是需要對(duì)當(dāng)前View繪制兩遍

3. Allocation Tracker

Android系統(tǒng)會(huì)依據(jù)內(nèi)存中不同的內(nèi)存數(shù)據(jù)類(lèi)型分別執(zhí)行不同的GC操作,常見(jiàn)的導(dǎo)致GC頻繁執(zhí)行的原因主要可能是因?yàn)槎虝r(shí)間內(nèi)有大量頻繁的對(duì)象創(chuàng)建與釋放操作,也就是俗稱(chēng)的內(nèi)存抖動(dòng)現(xiàn)象,或者短時(shí)間內(nèi)已經(jīng)存在大量?jī)?nèi)存占用介于閾值邊緣,每當(dāng)有新對(duì)象創(chuàng)建時(shí)都會(huì)導(dǎo)致超越閾值觸發(fā)GC操作。
Allocation Tracker,內(nèi)存分配跟蹤器,可以記錄代碼運(yùn)行過(guò)程中的內(nèi)存分配情況,包括已分配對(duì)象的調(diào)用棧、大小、代碼位置等,可以幫助我們發(fā)現(xiàn)在相同的調(diào)用棧中,短時(shí)間迅速分配與回收的大量相似對(duì)象,也可以幫助我們發(fā)現(xiàn)代碼中可能對(duì)內(nèi)存性能產(chǎn)生不良影響的地方。

1)打開(kāi)Android Studio中的Android Monitor控制器,進(jìn)入Memory內(nèi)存監(jiān)控窗口,點(diǎn)擊如下所示的Start Allocation Tracking按鈕,開(kāi)始跟蹤

圖3-2 啟動(dòng)Allocation Tracking

2)操作手機(jī)相關(guān)待檢測(cè)頁(yè)面,執(zhí)行相應(yīng)路徑代碼,此時(shí)Allocation Tracker會(huì)在后臺(tái)不斷記錄,注意操作時(shí)間不要過(guò)長(zhǎng),否則會(huì)產(chǎn)生過(guò)度的記錄,不便于發(fā)現(xiàn)問(wèn)題。然后再點(diǎn)擊一次Start Allocation Tracking按鈕,停止跟蹤

圖3-3 Allocation Tracking結(jié)果

3)最終會(huì)形成上圖所示的陰影區(qū)域,表示內(nèi)存跟蹤的時(shí)間段,然后會(huì)生存以.alloc命名的文件,默認(rèn)會(huì)以線程來(lái)分組

圖3-4 Allocation Tracking日志

4)可選擇Group by Method或Group by Allocator,每組內(nèi)的數(shù)據(jù)默認(rèn)按照對(duì)象的分配順序排列,可點(diǎn)擊每條展開(kāi),并可最終跟蹤到最底層調(diào)用,Count表示分配的內(nèi)存的次數(shù),Size表示分配內(nèi)存的大小

圖3-5 結(jié)果排序與調(diào)用堆棧

5)若選擇Group by Allocator,則按照包名來(lái)分組,組內(nèi)排序和使用與上述相同

圖3-6 按包名分組

6)標(biāo)題欄有個(gè)統(tǒng)計(jì)按鈕,點(diǎn)擊后,可以生存炫酷的內(nèi)存分配輪胎圖或柱狀圖,默認(rèn)是輪胎圖,輪胎圖是以圓心為起點(diǎn),最外層是其內(nèi)存實(shí)際分配的對(duì)象,每一個(gè)同心圓可能被分割成多個(gè)部分,代表了其不同的子孫,每一個(gè)同心圓代表他的一個(gè)后代,每個(gè)分割的部分代表了某一帶人有多人,你雙擊某個(gè)同心圓中某個(gè)分割的部分,會(huì)變成以你點(diǎn)擊的那一代為圓心再向外展開(kāi)。如果想回到原始狀態(tài),雙擊圓心就可以了

圖3-7 內(nèi)存分配輪胎圖

4. TraceView

TraceView 是 Android 平臺(tái)特有的數(shù)據(jù)采集和分析工具,它主要用于分析 Android 中應(yīng)用程序的 hotspot。TraceView 本身只是一個(gè)數(shù)據(jù)分析工具,而數(shù)據(jù)的采集則需要使用 Android SDK 中的 Debug 類(lèi)或者利用Android Device Monitor工具:

  • 在一些關(guān)鍵代碼段開(kāi)始前調(diào)用 Android SDK 中 Debug 類(lèi)的 startMethodTracing 函數(shù),并在關(guān)鍵代碼段結(jié)束前調(diào)用 stopMethodTracing 函數(shù)。這兩個(gè)函數(shù)運(yùn)行過(guò)程中將采集運(yùn)行時(shí)間內(nèi)該應(yīng)用所有線程(注意,只能是 Java 線程)的函數(shù)執(zhí)行情況,并將采集數(shù)據(jù)保存到 /mnt/sdcard/ 下的一個(gè)文件中,然后利用 SDK 中的TraceView工具來(lái)分析這些數(shù)據(jù)
  • 借助Android Device Monitor工具,采集系統(tǒng)中某個(gè)正在運(yùn)行的進(jìn)程的函數(shù)調(diào)用信息。對(duì)開(kāi)發(fā)者而言,此方法適用于沒(méi)有目標(biāo)應(yīng)用源代碼的情況

1)打開(kāi)Android Studio,進(jìn)入Tools->Android->Android Device Monitor,選擇相關(guān)進(jìn)程,并點(diǎn)擊Start Method Tracing按鈕,開(kāi)始追蹤

圖3-8 Start Method Tracing

2)操作應(yīng)用待檢測(cè)部分,不要時(shí)間太久,然后再次點(diǎn)擊Start Method Tracing按鈕,停止追蹤,便可自動(dòng)生成.trace文件

圖3-9 Trace文件

TraceViewUI 劃分為上下兩個(gè)面板,Timeline Panel(時(shí)間線面板)和 Profile Panel(分析面板),Timeline Panel 又可細(xì)分為左右兩個(gè) Panel:

左邊 Panel 顯示的是測(cè)試數(shù)據(jù)中所采集的線程信息
右邊 Pane 所示為時(shí)間線,時(shí)間線上是每個(gè)線程測(cè)試時(shí)間段內(nèi)所涉及的函數(shù)調(diào)用信息。這些信息包括函數(shù)名、函數(shù)執(zhí)行時(shí)間等

下半部分的Profile Panel 是 TraceView 的核心界面,主要展示了某個(gè)線程(先在 Timeline Panel 中選擇線程)中各個(gè)函數(shù)調(diào)用的情況,包括 CPU 使用時(shí)間、調(diào)用次數(shù)等信息。而這些信息正是查找 hotspot 的關(guān)鍵依據(jù)。下表是Profile Panel 中比較重要的列名及其描述:

圖3-10 Profile Panel列名與描述

可點(diǎn)擊上述任一排序項(xiàng)進(jìn)行排序

3)雙擊任一條記錄,可展開(kāi)詳細(xì)信息,包括調(diào)用者(Parents)和子函數(shù)(Children),可詳細(xì)的對(duì)調(diào)用次數(shù)過(guò)多的函數(shù)和每次執(zhí)行時(shí)間過(guò)長(zhǎng)的函數(shù)做排查,便于發(fā)現(xiàn)HotSpot

二、布局優(yōu)化

1. 抽象布局標(biāo)簽

1)<include>標(biāo)簽
include標(biāo)簽常用于將布局中的公共部分提取出來(lái)供其他layout共用,以實(shí)現(xiàn)布局模塊化,這在布局編寫(xiě)方便提供了大大的便利。

2)<viewstub>標(biāo)簽
viewstub標(biāo)簽同include標(biāo)簽一樣可以用來(lái)引入一個(gè)外部布局,但是,viewstub引入的布局默認(rèn)不會(huì)擴(kuò)張,即既不會(huì)占用顯示也不會(huì)占用位置,從而在解析layout時(shí)節(jié)省cpu和內(nèi)存。viewstub常用來(lái)引入那些默認(rèn)不會(huì)顯示,只在特殊情況下顯示的布局,如進(jìn)度布局、網(wǎng)絡(luò)失敗顯示刷新布局、信息出錯(cuò)出現(xiàn)提示布局等。

3)<merge>標(biāo)簽
在使用了include后可能導(dǎo)致布局嵌套過(guò)多,多余不必要的layout節(jié)點(diǎn),從而導(dǎo)致解析變慢,merge標(biāo)簽可用于兩種典型情況:

  • 布局頂結(jié)點(diǎn)是FrameLayout且不需要設(shè)置background或padding等屬性,可以用merge代替,因?yàn)锳ctivity內(nèi)容試圖的parent view就是個(gè)FrameLayout,所以可以用merge消除只剩一個(gè)
  • 某布局作為子布局被其他布局include時(shí),使用merge當(dāng)作該布局的頂節(jié)點(diǎn),這樣在被引入時(shí)頂結(jié)點(diǎn)會(huì)自動(dòng)被忽略,而將其子節(jié)點(diǎn)全部合并到主布局中

2. 去除不必要的嵌套和View節(jié)點(diǎn)

1)首次不需要使用的節(jié)點(diǎn)設(shè)置為GONE或使用viewstub
2)使用RelativeLayout代替LinearLayout

3. 減少不必要的infalte

1)對(duì)于inflate的布局可以直接緩存,用全部變量代替局部變量,避免下次需再次inflate

三、代碼優(yōu)化

1. 降低執(zhí)行時(shí)間

包括:緩存、數(shù)據(jù)存儲(chǔ)優(yōu)化、算法優(yōu)化、JNI、邏輯優(yōu)化等幾種優(yōu)化方式 。

1)緩存
緩存主要包括對(duì)象緩存、IO緩存、網(wǎng)絡(luò)緩存、DB緩存,對(duì)象緩存能減少內(nèi)存的分配,IO緩存減少磁盤(pán)的讀寫(xiě)次數(shù),網(wǎng)絡(luò)緩存減少網(wǎng)絡(luò)傳輸,DB緩存較少Database的訪問(wèn)次數(shù)。
在內(nèi)存、文件、數(shù)據(jù)庫(kù)、網(wǎng)絡(luò)的讀寫(xiě)速度中,內(nèi)存都是最優(yōu)的,且速度數(shù)量級(jí)差別,所以盡量將需要頻繁訪問(wèn)或訪問(wèn)一次消耗較大的數(shù)據(jù)存儲(chǔ)在緩存中。
Android中常使用緩存:

  • 線程池,對(duì)線程的緩存
  • Android圖片緩存
  • 消息緩存,通過(guò)handler.obtainMessage復(fù)用之前的message,如下:handler.sendMessage(handler.obtainMessage(0, object));
  • ListView緩存
  • 網(wǎng)絡(luò)緩存,數(shù)據(jù)庫(kù)緩存http response,根據(jù)http頭信息中的Cache-Control域確定緩存過(guò)期時(shí)間
  • 文件IO緩存,使用具有緩存策略的輸入流,BufferedInputStream替代InputStream,BufferedReader替代Reader,BufferedReader替代BufferedInputStream.對(duì)文件、網(wǎng)絡(luò)IO皆適用
  • layout緩存
  • 其他需要頻繁訪問(wèn)或訪問(wèn)一次消耗較大的數(shù)據(jù)緩存

2)數(shù)據(jù)存儲(chǔ)優(yōu)化

包括數(shù)據(jù)類(lèi)型、數(shù)據(jù)結(jié)構(gòu)的選擇:

  • 數(shù)據(jù)類(lèi)型選擇
    字符串拼接用StringBuilder代替String,在非并發(fā)情況下用StringBuilder代替StringBuffer。
    64位類(lèi)型如long double的處理比32位如int慢。
    使用SoftReference、WeakReference相對(duì)正常的強(qiáng)應(yīng)用來(lái)說(shuō)更有利于系統(tǒng)垃圾回收。
    final類(lèi)型存儲(chǔ)在常量區(qū)中讀取效率更高。
    枚舉值相比常量會(huì)占用更大的內(nèi)存空間和運(yùn)行開(kāi)銷(xiāo),適用于強(qiáng)類(lèi)型安全的場(chǎng)景,對(duì)于普通常量的匯總,就不一定需要。
    盡量使用原始數(shù)據(jù)類(lèi)型,避免頻繁的自動(dòng)裝箱。
    避免使用非靜態(tài)內(nèi)部類(lèi),當(dāng)創(chuàng)建并實(shí)例化了一個(gè)非靜態(tài)內(nèi)部類(lèi),內(nèi)部就包含一個(gè)指向外部類(lèi)型的隱含引用,如果這個(gè)內(nèi)部類(lèi)實(shí)例比外部類(lèi)型存活的時(shí)間還長(zhǎng),那即使不需要這個(gè)外部類(lèi)型,它還是會(huì)被保存在內(nèi)存中。
    LocalBroadcastManager代替普通BroadcastReceiver,效率和安全性都更高。
    如果對(duì)象中某個(gè)方法的調(diào)用不依賴(lài)于該對(duì)象的實(shí)例化,則建議將該方法定義成static,可提高訪問(wèn)與調(diào)用效率。
  • 數(shù)據(jù)結(jié)構(gòu)選擇
    常見(jiàn)的數(shù)據(jù)結(jié)構(gòu)選擇如:
    ArrayList和LinkedList的選擇,ArrayList隨機(jī)訪問(wèn)更快,LinkedList更占內(nèi)存、隨機(jī)插入刪除更快速、擴(kuò)容效率更高。
    HashMap、LinkedHashMap、HashSet的選擇,HashMap為鍵值對(duì)數(shù)據(jù)結(jié)構(gòu),LinkedHashMap可以記住加入次序的hashMap,HashSet不允許重復(fù)元素。
    HashMap、WeakHashMap選擇,WeakHashMap中元素可在適當(dāng)時(shí)候被系統(tǒng)垃圾回收器自動(dòng)回收,所以適合在內(nèi)存緊張型中使用。
    Collections.synchronizedMap和ConcurrentHashMap的選擇,ConcurrentHashMap為細(xì)分鎖,鎖粒度更小,并發(fā)性能更優(yōu)。Collections.synchronizedMap為對(duì)象鎖,自己添加函數(shù)進(jìn)行鎖控制更方便。
    Android也提供了一些性能更優(yōu)的數(shù)據(jù)類(lèi)型,如SparseArray、SparseBooleanArray、SparseIntArray、Pair。
    Sparse系列的數(shù)據(jù)結(jié)構(gòu)是為key為int情況的特殊處理,采用二分查找及簡(jiǎn)單的數(shù)組存儲(chǔ),加上不需要泛型轉(zhuǎn)換的開(kāi)銷(xiāo),相對(duì)Map來(lái)說(shuō)性能更優(yōu)。
    當(dāng)需要存儲(chǔ)一對(duì)元數(shù)據(jù)數(shù)組,例如(Foo,Bar),采用兩個(gè)平行的二維數(shù)組Foo[ ]和Bar[ ],要比直接存儲(chǔ)對(duì)象(Foo,Bar)數(shù)組的效率高。
    使用增強(qiáng)的for-each循環(huán)對(duì)實(shí)現(xiàn)了iterable接口的collections以及數(shù)組做遍歷,for (Foo a : mArray)和for (int i = 0; i < len; ++i)效率最高,避免使用for (int i = 0; i < mArray.length; ++i)

3)算法優(yōu)化
這個(gè)主題比較大,需要具體問(wèn)題具體分析,盡量不用O(n*n)時(shí)間復(fù)雜度以上的算法,必要時(shí)候可用空間換時(shí)間。查詢(xún)考慮hash和二分,盡量不用遞歸。

4)JNI
Android應(yīng)用程序大都通過(guò)Java開(kāi)發(fā),需要編譯器將Java字節(jié)碼轉(zhuǎn)換成本地代碼運(yùn)行,而本地代碼可以直接由設(shè)備管理器直接執(zhí)行,節(jié)省了中間步驟,所以執(zhí)行速度更快。不過(guò)需要注意從Java空間切換到本地空間需要開(kāi)銷(xiāo),同時(shí)編譯器也能生成優(yōu)化的本地代碼,所以糟糕的本地代碼不一定性能更優(yōu)。

5)邏輯優(yōu)化
主要是理清程序業(yè)務(wù)邏輯,減少不必要的操作和分支判斷等。

2. 異步,利用多線程提高TPS

充分利用多核CPU優(yōu)勢(shì),利用線程解決密集型計(jì)算、IO、網(wǎng)絡(luò)等操作。

3. 提前或延遲操作,錯(cuò)開(kāi)時(shí)間段提高TPS

1)延遲操作
不在Activity、Service、BroadcastReceiver的生命周期等對(duì)響應(yīng)時(shí)間敏感函數(shù)中執(zhí)行耗時(shí)操作,可適當(dāng)delay,Java中延遲操作可使用ScheduledExecutorService, Android中除了支持ScheduledExecutorService之外,還有一些delay操作,如handler.postDelayed,handler.postAtTime,handler.sendMessageDelayed,View.postDelayed,AlarmManager定時(shí)等。

2)提前操作
對(duì)于第一次調(diào)用較耗時(shí)操作,可統(tǒng)一放到初始化中,將耗時(shí)提前。如得到壁紙wallpaperManager.getDrawable();

四、關(guān)于內(nèi)存泄漏

1. 使用Android Studio Java Heap Dump檢測(cè)

1)打開(kāi)Android Studio,進(jìn)入Android Device Monitor,切換到Memory監(jiān)控窗口
2)選擇待調(diào)試進(jìn)程com.baidu.ls.waimai,并操作App中可能發(fā)生內(nèi)存泄漏的地方,點(diǎn)擊Initiate GC按鈕進(jìn)行強(qiáng)制GC,然后點(diǎn)擊Dump Java Heap按鈕,導(dǎo)出.hprof格式文件

圖4-1 Dump Java Heap

3)打開(kāi).hprof文件,可選擇Package Tree View,選擇應(yīng)用包內(nèi)想要查看的對(duì)象類(lèi)型,可在右側(cè)Instance窗口查看該類(lèi)型已存在的實(shí)例化對(duì)象數(shù)量,在下面Reference Tree窗口查看該對(duì)象引用路徑

圖4-2 hprof文件分析

4)上面窗口有個(gè)Total Count篩選項(xiàng),可以看到該對(duì)象目前在內(nèi)存中存在的數(shù)量,若某個(gè)對(duì)象數(shù)量存在異常,比如此時(shí)App所有的點(diǎn)菜頁(yè)已關(guān)閉,但ShopMenuFragment數(shù)量不為0,則該對(duì)象很可能發(fā)生內(nèi)存泄漏,需要根據(jù)reference tree做相關(guān)排查,并找到最終的GC Root

2. 使用LeakCanary檢測(cè)

LeakCanary是一個(gè)用于檢測(cè)內(nèi)存泄露的開(kāi)源類(lèi)庫(kù),提供了一種便捷、自動(dòng)的檢測(cè)方式,并產(chǎn)出可視化報(bào)告,以很直白的方式將內(nèi)存泄露鏈條展示給我們。

1)接入,在build.gradle中根據(jù)不同的編譯方式,選擇加入不同的引用方式,

dependencies { 
        debugCompile 'com.squareup.leakcanary:leakcanary-android:1.3' 
        releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.3' 
     }

2)LeakCanary.install(context) 會(huì)返回一個(gè)預(yù)定義的 RefWatcher,同時(shí)也會(huì)啟用一個(gè) ActivityRefWatcher,用于自動(dòng)監(jiān)控調(diào)用 Activity.onDestroy() 之后泄露的 activity。
在Application中進(jìn)行配置:

public class ExampleApplication extends Application { 
  public static RefWatcher getRefWatcher(Context context) { 
    ExampleApplication application = (ExampleApplication) context.getApplicationContext(); 
    return application.refWatcher; 
  } 

  private RefWatcher refWatcher; 
  @Override public void onCreate() { 
    super.onCreate(); 
    refWatcher = LeakCanary.install(this); 
  } 
} 

3)使用RefWatcher監(jiān)控Fragment

public abstract class BaseFragment extends Fragment { 
  @Override public void onDestroy() { 
    super.onDestroy(); 
    RefWatcher refWatcher = ExampleApplication.getRefWatcher(getActivity()); 
    refWatcher.watch(this); 
  } 
} 

4)產(chǎn)出可視化報(bào)告

圖4-3 LeakCanary可視化報(bào)告

3. 常見(jiàn)避免內(nèi)存泄露方式

1)對(duì)Activity等組件的引用應(yīng)該控制在Activity的生命周期之內(nèi),如果不能就考慮使用getApplicationContext或者getApplication,以避免Activity被外部長(zhǎng)生命周期的對(duì)象引用而泄露
2)盡量不要在靜態(tài)變量或者靜態(tài)內(nèi)部類(lèi)中使用非靜態(tài)外部成員變量(包括
context ),即使要使用,也要考慮適時(shí)把外部成員變量置空;也可以在內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的變量
3)對(duì)于生命周期比Activity長(zhǎng)的內(nèi)部類(lèi)對(duì)象,并且內(nèi)部類(lèi)中使用了外部類(lèi)的成員變量,可以這樣做避免內(nèi)存泄漏:將內(nèi)部類(lèi)改為靜態(tài)內(nèi)部類(lèi)、靜態(tài)內(nèi)部類(lèi)中使用弱引用來(lái)引用外部類(lèi)的成員變量
4)Handler持有的引用對(duì)象最好使用弱引用,資源釋放時(shí)也可以清空 Handler 里面的消息。比如在 Activity onStop 或者 onDestroy 的時(shí)候,取消掉該 Handler 對(duì)象的 Message和 Runnable
5)在 Java 的實(shí)現(xiàn)過(guò)程中,也要考慮其對(duì)象釋放,最好的方法是在不使用某對(duì)象時(shí),顯式地將此對(duì)象賦值為 null,比如使用完Bitmap 后先調(diào)用 recycle(),再賦為null,清空對(duì)圖片等資源有直接引用或者間接引用的數(shù)組(使用 array.clear() ; array = null)等,最好遵循誰(shuí)創(chuàng)建誰(shuí)釋放的原則
6)正確關(guān)閉資源,對(duì)于使用了BraodcastReceiver,ContentObserver,F(xiàn)ile,游標(biāo) Cursor,Stream,Bitmap等資源的使用,應(yīng)該在Activity銷(xiāo)毀時(shí)及時(shí)關(guān)閉或者注銷(xiāo)
7)保持對(duì)對(duì)象生命周期的敏感,特別注意單例、靜態(tài)對(duì)象、全局性集合等的生命周期

六、參考鏈接

  1. Android官方性能優(yōu)化:
    https://developer.android.com/training/best-performance.html
  2. Android官方性能典范教程:
    http://hukai.me/android-performance-patterns/
  3. 性能優(yōu)化博客合輯:
    https://github.com/Juude/awesome-android-performance
  4. Trinea性能優(yōu)化合輯:
    http://www.trinea.cn/android/performance/
  5. Android性能優(yōu)化多途徑實(shí)現(xiàn):
    http://blog.csdn.net/yanbober/article/details/48394201
  6. Android Studio官方profile工具使用:
    https://developer.android.com/studio/profile/index.html
  7. Android內(nèi)存泄露總結(jié):
    https://yq.aliyun.com/articles/3009
  8. Android TraceView使用:
    http://www.cnblogs.com/sunzn/p/3192231.html
  9. LeakCanary使用:
    http://m.itdecent.cn/p/7db231163168
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容