導(dǎo)語(yǔ)
隨著Android應(yīng)用增多榛斯,功能越來越復(fù)雜蔓钟,布局也越來越豐富了永票,而這些也成為了阻礙一個(gè)應(yīng)用流暢運(yùn)行,因此滥沫,對(duì)復(fù)雜的功能進(jìn)行性能優(yōu)化是創(chuàng)造高質(zhì)量應(yīng)用的基礎(chǔ)侣集,本章節(jié)將為大家展示幾種性能優(yōu)化的方法,幫助開發(fā)者快速的構(gòu)建運(yùn)行速度快兰绣,相應(yīng)速度快的應(yīng)用程序肚吏。
主要內(nèi)容
- 布局優(yōu)化
- 內(nèi)存優(yōu)化
- 使用各種工具進(jìn)行分析,優(yōu)化
具體內(nèi)容
布局優(yōu)化
Android UI渲染機(jī)制
- 人眼所感覺的流暢畫面狭魂,需要畫面的幀數(shù)達(dá)到40幀每秒到60幀每秒罚攀,最佳fps大概在60fps左右
- 16ms也就是1000ms中顯示60幀畫面的單位時(shí)間,即1000/60雌澄,所以UI渲染時(shí)間間隔為16ms斋泄。
- 如果不能在16ms內(nèi)繪制完成,則會(huì)出現(xiàn)卡頓的效果镐牺。
- 通過開發(fā)者選項(xiàng)炫掐,選擇”Profile GPU Rendering”并選中”O(jiān)n screen as bars”的選項(xiàng),可以打開UI渲染時(shí)間的工具睬涧。
- 每一條柱狀線包含三部分募胃,藍(lán)色代表測(cè)量繪制Display List的時(shí)間旗唁,紅色代表OpenGL渲染Display List的時(shí)間,黃色代表CPU等待GPU處理的時(shí)間痹束。
- 中間的綠色橫線代表繪制間隔時(shí)間16ms检疫,所以盡量保持在綠色之下。
避免Overdraw
- Overdraw祷嘶,過度繪制會(huì)浪費(fèi)很多的CPU屎媳、GPU資源。
- Android系統(tǒng)在開發(fā)者選項(xiàng)中提供了這樣一個(gè)檢測(cè)工具论巍,“Enable GPU Overdraw”烛谊。
- 通過這個(gè)工具可以查看當(dāng)前區(qū)域中繪制次數(shù),從而盡量?jī)?yōu)化繪圖層次嘉汰,盡量增大藍(lán)色的區(qū)域丹禀,減少紅色的區(qū)域。
優(yōu)化布局層級(jí)
- Android中鞋怀,系統(tǒng)對(duì)View進(jìn)行測(cè)量湃崩、布局和繪制時(shí),都是通過對(duì)View數(shù)的遍歷來進(jìn)行操作的接箫。
- 如果View樹太高攒读,就會(huì)嚴(yán)重影響測(cè)量、布局和繪制的速度辛友,因此適當(dāng)減少View樹的高度薄扁,Google建議View樹高度不宜超過10層。
- 現(xiàn)在版本的Android废累,Google已經(jīng)使用RelativeLayout來替代LinearLayout作為默認(rèn)的根布局邓梅,其原因就是降低LinearLayout嵌套所產(chǎn)生布局樹高度。
避免嵌套過多無用布局
- 使用< include>標(biāo)簽重用Layout邑滨。
- 使用< ViewStub>實(shí)現(xiàn)View的延遲加載日缨。
< include>標(biāo)簽重用,首先編寫common_ui.xml文件:
<?xml version="1.0" encoding="utf-8"?>
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="0dp"
android:layout_height="0dp"
android:text="this is a common ui"
android:textSize="30sp" />
接著在主布局中引用:
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
layout="@layout/common_ui"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</RelativeLayout>
這樣掖看,common_ui就被引用了匣距,其中include標(biāo)簽里面屬性會(huì)覆蓋common_ui里面的屬性。
使用< ViewStub>實(shí)現(xiàn)View的延遲加載哎壳,< ViewStub>是個(gè)輕量級(jí)的組件毅待,它不僅不可視,而且大小為0归榕,首先編寫not_often_use.xml文件尸红。
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="not often use"
android:textSize="30sp" />
</RelativeLayout>
接著在主布局中引用:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ViewStub
android:id="@+id/not_often_use"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout="@layout/not_often_use" />
</RelativeLayout>
這個(gè)時(shí)候是在布局中見不到這個(gè)ViewStub的布局的,有兩種方法可以來重新顯示這個(gè)View:
- VISBLE
ViewStub mViewStub = (ViewStub) findViewById(R.id.not_often_use);
mViewStub.setVisibility(View.VISIBLE);
- inflate
ViewStub mViewStub = (ViewStub) findViewById(R.id.not_often_use);
View inflateView = mViewStub.inflate();
這兩種方法唯一區(qū)別:
inflate()方法可以返回引用布局,從而可以通過View.findViewById()方法找到相對(duì)應(yīng)控件外里。
< ViewStub>標(biāo)簽與設(shè)置View.GONE有什么區(qū)別:
它們的共同點(diǎn)就是在初始化時(shí)不會(huì)顯示怎爵,但是< ViewStub>標(biāo)簽只會(huì)在顯示時(shí),才會(huì)去渲染整個(gè)布局盅蝗,而View.GONE在初始化布局樹的時(shí)候就已經(jīng)添加在布局樹上了鳖链,相比之下,< ViewStub>標(biāo)簽的布局具有更高的效率风科。
Hierarchy Viewer
- 通常情況下,Hierarchy Viewer無法在真機(jī)上使用乞旦,只能在模擬器上使用贼穆。
- Google大神提供了一個(gè)開源軟件View Server,讓普通手機(jī)也能使用Hierarchy Viewer兰粉,軟件網(wǎng)址http://github.com/romainguy/ViewServer故痊。
- Hierarchy Viewer位于sdk\tools目錄下,打開hierarchyviewer.bat啟動(dòng)程序玖姑。
- 關(guān)于Hierarchy Viewer的使用可查看下面的博客:Hierarchy Viewer愕秫。
內(nèi)存優(yōu)化
什么是內(nèi)存
由于Android的沙箱機(jī)制,每個(gè)應(yīng)用所分配的內(nèi)存大小是有限度的焰络,內(nèi)存太低就會(huì)觸發(fā)LMK:Low Memory Killer機(jī)制戴甩,我們所說的內(nèi)存是指手機(jī)的RAM,它包括以下幾個(gè)部分:
寄存器(Registers)
速度最快的存儲(chǔ)場(chǎng)所闪彼,因?yàn)榧拇嫫魑挥谔幚砥鲀?nèi)部甜孤,在程序中無法控制
- 棧(Stack)
存放基本類型的數(shù)據(jù)和對(duì)象的引用,但對(duì)像本身不存放在棧中畏腕,而是存放在堆中缴川。 - 堆(Heap)
堆內(nèi)存用來存放由new創(chuàng)建的對(duì)象和數(shù)組,在堆中分配的內(nèi)存描馅,由Java虛擬機(jī)的自動(dòng)垃圾回收(GC)來管理把夸。 - 靜態(tài)存儲(chǔ)區(qū)域(Static Field)
靜態(tài)存儲(chǔ)區(qū)域就是指在固定的位置存放應(yīng)用程序運(yùn)行時(shí)一直存在的數(shù)據(jù),Java在內(nèi)存中專門劃分了一個(gè)靜態(tài)存儲(chǔ)區(qū)域來管理一些特殊的數(shù)據(jù)變量如靜態(tài)的數(shù)據(jù)變量铭污。 - 常量池(Constant Pool)
JVM虛擬機(jī)必須為每個(gè)被裝載的類型維護(hù)一個(gè)常量池恋日,常量池就是該類型所用到常量的一個(gè)有序集合,包括直接常量(基本類型嘹狞,String)和對(duì)其他類型谚鄙、字段和方法的符號(hào)引用。
這些概念中最容易搞錯(cuò)的是堆和棧的區(qū)分:
- 棧:當(dāng)定義一個(gè)變量時(shí)刁绒,棧會(huì)為該變量分配內(nèi)存空間闷营,當(dāng)該變量作用域結(jié)束后,這部分內(nèi)存控件會(huì)被用作新的空間進(jìn)行分配。
- 堆:使用new的方式創(chuàng)建一個(gè)變量傻盟,那么就會(huì)在堆中為這個(gè)對(duì)象分配內(nèi)存控件速蕊,即使該對(duì)象的作用域結(jié)束,這部分內(nèi)存也不會(huì)立即被回收娘赴,而是等待系統(tǒng)的GC進(jìn)行回收规哲。
我們可以通過代碼分析Heap中的內(nèi)存狀態(tài):
ActivityManager manager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
int heapSize = manager.getLargeMemoryClass();
獲取Android系統(tǒng)內(nèi)存信息
- Process Stats:
系統(tǒng)內(nèi)存監(jiān)視服務(wù),通過”Setting-Developer-options-Process Stats”來開啟這個(gè)功能诽表,也可以使用Dumpsys命令來獲取這些信息:
adb shell dumpsys procstats
- Meminfo:
內(nèi)存監(jiān)視工具唉锌,通過”Settings-Apps-Running”中打開這個(gè)界面,也可以使用Dumpsys命令來獲取這些信息:
adb shell dumpsys meminfo
內(nèi)存回收
- Java對(duì)于C竿奏、C++這類語(yǔ)言最大的優(yōu)勢(shì)就是不用手動(dòng)管理系統(tǒng)資源袄简,Java創(chuàng)建了垃圾收集器線程來自動(dòng)進(jìn)行資源的管理。
- Java的GC是系統(tǒng)自動(dòng)進(jìn)行的泛啸,但何時(shí)進(jìn)行卻是開發(fā)者無法控制的绿语,即使調(diào)用System.gc()方法,也只是建議系統(tǒng)進(jìn)行GC候址,系統(tǒng)不一定會(huì)采取你的建議吕粹。
- 盡管再?gòu)?qiáng)大的算法,也難免存在部分對(duì)象忘記回收的想象岗仑,這就是造成內(nèi)存泄露的原因匹耕。
內(nèi)存優(yōu)化實(shí)例
- Bitmap優(yōu)化:Bitmap是造成內(nèi)存占用過高或OOM的最大威脅。
- 使用適當(dāng)分辨率和大小的圖片
- 及時(shí)回收內(nèi)存
- 使用圖片緩存
- 代碼優(yōu)化:任何Java類占用大約500字節(jié)的內(nèi)存空間荠雕,創(chuàng)建一個(gè)類的實(shí)例會(huì)消耗大概15字節(jié)的內(nèi)存
- 對(duì)常量使用static修飾符泌神。
- 使用靜態(tài)方法,靜態(tài)方法會(huì)比普通方法提高15%左右的訪問速度舞虱。
- 減少不必要的成員變量 ,這點(diǎn)在Android Lint工具上已經(jīng)集成檢測(cè)了欢际,如果一個(gè)變量可以定義為局部變量,則會(huì)建議你不要定義為成員變量矾兜。
- 減少不必要的對(duì)象损趋,使用基礎(chǔ)類型會(huì)比使用對(duì)象更加節(jié)省資源, 同時(shí)更應(yīng)該避免頻繁創(chuàng)建短作用域的變量。
- 盡量不要使枚舉椅寺、少使用迭代器浑槽。
- 對(duì)Cursor、Receiver返帕、Sensor桐玻、Fiie等對(duì)象,要非常注意對(duì)它們的創(chuàng)建荆萤,回收與注冊(cè)镊靴、解注冊(cè)铣卡。
- 避免使用IOC框架,IOC通常使用注解偏竟、反射來進(jìn)行實(shí)現(xiàn)煮落,雖然現(xiàn)在Java對(duì)反射的效率已經(jīng)進(jìn)行了很好的優(yōu)化,但大量使用反射依然會(huì)帶來性能的下降踊谋。
- 使用RenderScript蝉仇、OpenGL來進(jìn)行非常復(fù)雜的繪圖操作。
- 使用SurfaceView來替代View進(jìn)行大量殖蚕、頻繁的繪圖操作轿衔。
- 盡量使用視圖緩存,而不是每次都執(zhí)行inflaler()方法解析視圖睦疫。
Lint工具
Android Lint工具是Android Studio中集成的一個(gè)Android代碼提示工具害驹。
Lint的功能非常的強(qiáng)大,大家應(yīng)該養(yǎng)成寫完代碼之后檢查lint的習(xí)慣笼痛,這不僅可以讓我們及時(shí)的發(fā)現(xiàn)一些隱藏的問題裙秋,而且琅拌,更讓我們養(yǎng)成良好的代碼風(fēng)格缨伊。
使用Android Studio的Memory Monitor工具
Memory Monitor工具是Android studio上的一個(gè)內(nèi)存監(jiān)視工具,他可以很好的幫助我們進(jìn)行內(nèi)存實(shí)時(shí)分析进宝,通過點(diǎn)擊Android studio右下角的Memory Monitor標(biāo)簽就可以進(jìn)行查看了刻坊。
使用TraceView工具優(yōu)化APP性能
TraceView是一個(gè)Android下的可視化性能調(diào)查工具,用來分析TraceView日志党晋。
生成TraceView日志的兩種方法
通過代碼生成精準(zhǔn)范圍的TraceView日志:
- 通過調(diào)用Debug.startMethodTracing()方法開啟監(jiān)聽谭胚,調(diào)用Debug.stopMethodTracing()方法結(jié)束監(jiān)聽,通常會(huì)在onCreate時(shí)開啟監(jiān)聽未玻,在onDestroy時(shí)結(jié)束監(jiān)聽灾而。
- TraceView日志將會(huì)保存到”/sdcard/dmtrace.trace”目錄下,因此需要在權(quán)限中增加:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
當(dāng)然除了默認(rèn)輸出日志名扳剿,還可以自定義路徑和日志名旁趟,最后通過ADB命令,將日志文件導(dǎo)出到本地:
adb pull /sdcard/trace_log.trace/local/LOG/
通過Android Device Monitor生成TraceView日志:
- 打開Android Device Monitor工具庇绽,選擇要調(diào)試的進(jìn)程锡搜,點(diǎn)擊工具欄中的”start method profiling”按鈕,打開瞧掺,再次點(diǎn)擊”start method profiling”按鈕即可結(jié)束
- TraceView提供兩種監(jiān)聽方式:
- 整體監(jiān)聽:跟蹤每個(gè)方法執(zhí)行的全部過程耕餐,這種方式資源消耗比較大
- 抽樣監(jiān)聽:按照指定的頻率來進(jìn)行抽樣調(diào)查,這種方式需要執(zhí)行較長(zhǎng)時(shí)間來獲取較精準(zhǔn)的樣本數(shù)據(jù)
打開TraceView
- 可以使用SDK中的”sdk\tools\traceview.bat”工具打開辟狈。
- 可以在Android Device Monitor工具下肠缔,的File菜單選擇Open File打開。
分析TraceView
- 時(shí)間軸區(qū)域:顯示了不同線程在不同時(shí)間段內(nèi)的執(zhí)行情況 。
- 在時(shí)間軸中桩砰,每一行都代表了一個(gè)獨(dú)立的線程拓春。
- 使用鼠標(biāo)滾輪可以放大時(shí)間軸。
- 不同色塊代表不同的執(zhí)行方法亚隅,色塊的長(zhǎng)度硼莽,代表了方法所執(zhí)行的時(shí)間。
- Profile區(qū)域:顯示你選擇的色塊所代表的方法在該色塊所處的時(shí)間段內(nèi)的性能分析 煮纵。
- Incl CPU Time:某方法占用CPU的時(shí)間懂鸵。
- Excl CPU Time:某方法本身(不包含子方法)占用CPU的時(shí)間。
- Incl Real Time:某方法真正執(zhí)行的時(shí)間行疏。
- Excl Real Time:某方法本身(不包含子方法)真正執(zhí)行的時(shí)間匆光。
- Calls+RecurCalls:調(diào)用次數(shù)+遞歸回調(diào)的次數(shù)。
使用MAT工具分析App內(nèi)存狀態(tài)
MAT工具是一個(gè)分析內(nèi)存的強(qiáng)力助手酿联。
生成HPROF文件
打開Android Device Monitor工具终息,選擇要監(jiān)聽的線程,并點(diǎn)擊菜單欄中的”Update Heap”按鈕贞让。
在Heap標(biāo)簽中點(diǎn)擊”Cause GC”按鈕周崭,就會(huì)顯示當(dāng)前內(nèi)存狀態(tài)。
這里有一 個(gè)判斷當(dāng)前是否存在內(nèi)存泄漏的小技巧:當(dāng)我們不停地點(diǎn)擊”Cause GC”按鈕的時(shí)喳张,如果”data object”一欄中的”Total size”有明顯變化续镇,就代表可能存在內(nèi)存泄漏。
上面是手動(dòng)查看Heap狀態(tài)销部,下面點(diǎn)擊菜單欄的”Dump HPROF File”按鈕摸航。
系統(tǒng)會(huì)生成一個(gè).hprof文件,默認(rèn)名為包名.hprof舅桩,不過還不能直接使用MAT工具查看酱虎,還需要進(jìn)行格式轉(zhuǎn)換,在SDK目錄的platform-tools目錄下擂涛,使用hprof-conv工具幫助轉(zhuǎn)換读串,命令如下:
D:\sdk\platform-tools>hprof-conv F:\Heap\com.handsome.heap.hprof heap.hprof
格式命令:”hprof-conv infile outfile”生成heap.hprof文件利用MAT工具就可以進(jìn)行內(nèi)存分析。
分析HPROF文件
打開MAT工具歼指,選擇”O(jiān)pen a Heap Dump”選項(xiàng)爹土,進(jìn)入分析:
- Histogram
- Histogram直方圖,用于顯示內(nèi)存中每個(gè)對(duì)象的數(shù)量踩身、大小和名稱
- 在選擇對(duì)象上單擊鼠標(biāo)右鍵胀茵,在彈出的快捷菜單中選擇”List objects-with incoming references”選項(xiàng)查看具體的對(duì)象
- Dominator Tree
- Dominator Tree支配樹會(huì)將內(nèi)存中的對(duì)象按照大小進(jìn)行排序,并顯示對(duì)象之間的引用結(jié)構(gòu)
- 對(duì)象已經(jīng)按照”Retained Heap”進(jìn)行排序了挟阻,即按照對(duì)象及其持有的引用的內(nèi)存總和進(jìn)行排序琼娘,通過分析內(nèi)存占用大的對(duì)象找出內(nèi)存消耗的原因
使用Dumpsys命令分析系統(tǒng)狀態(tài)
- Dumpsys命令的功能非常強(qiáng)大峭弟,可使用的參數(shù)配置也非常多,Dumpsys官方信息:https://source.android.com/devices/input/dumpsys.html脱拼。
- 使用Dumpsys命令瞒瘸,只需要輸入”adb shell dumpsys+參數(shù)”即可 。
- adb shell dumpsys activity:查看Activity棧的詳細(xì)信息熄浓。
- adb shell dumpsys meminfo:查看內(nèi)存信息情臭。
- adb shell dumpsys battery:查看電池信息。
- adb shell dumpsys package:查看包信息赌蔑。
- adb shell dumpsys wifi:顯示W(wǎng)i-Fi信息俯在。
- adb shell dumpsys alarm:顯示alarm信息。
- adb shell dumpsys procstats:顯示內(nèi)存信息娃惯。