前言:本章知識(shí)不是為具體的一個(gè)性能問(wèn)題提供詳細(xì)的解決方案,而是梳理和總結(jié)Android中性能優(yōu)化有關(guān)的問(wèn)題,讓我們?cè)谝院蟮拈_(kāi)發(fā)中遇到性能有關(guān)的問(wèn)題時(shí)能夠快速分析,定位,并解決問(wèn)題.

ANR異常
什么是ANR:
Application Not responding 應(yīng)用程序無(wú)響應(yīng),在android四大組件都是運(yùn)行在主線程當(dāng)中的,android的,ActivityManager和WindowManager會(huì)時(shí)刻監(jiān)視這應(yīng)用的響應(yīng)狀態(tài).如果因?yàn)樽枞?死鎖)或者耗時(shí)操作(Activity中5s內(nèi)不能響應(yīng)用戶,Recever中方法執(zhí)行時(shí)間超過(guò)10s),那么系統(tǒng)就會(huì)彈出ANR的對(duì)話框提示用戶目前應(yīng)用處于無(wú)響應(yīng)的狀態(tài).
為了避免ANR:
1.對(duì)于耗時(shí)操作都是通過(guò)AsyncTask,HandlerThread,IntentService去解決.避免UI線程的做耗時(shí)操作.
2.避免UI線程阻塞
分析ANR:
在發(fā)生Anr異常以后,dalvik虛擬機(jī)會(huì)在data/anr目錄下生成trace文件.用于分析Anr異常.
在trace文件中主要包含以下:
1.ANR進(jìn)程id,時(shí)間以及名稱(chēng)
2.線程的名稱(chēng),優(yōu)先級(jí),線程鎖id以及線程的狀態(tài),通過(guò)線程狀態(tài)可以知道耗時(shí)操作還是阻塞引起ANR
3.導(dǎo)致anr的調(diào)用棧信息.
舉個(gè)簡(jiǎn)單的例子:
DALVIK THREADS:
(mutexes: tll=0 tsl=0 tscl=0 ghl=0 hwl=0 hwll=0)
"main" prio=5 tid=1 NATIVE
| group="main" sCount=1 dsCount=0 obj=0x40025340 self=0xd180
| sysTid=1071 nice=0 sched=0/0 cgrp=default handle=-1344994080
| schedstat=( 2355584448 1199910712 3410 )
at java.net.InetAddress.getaddrinfo(Native Method)
at java.net.InetAddress.lookupHostByName(InetAddress.java:540)
at java.net.InetAddress.getAllByNameImpl(InetAddress.java:333)
at java.net.InetAddress.getAllByName(InetAddress.java:295)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:100)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection.<init>(HttpConnection.java:79)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnection$Address.connect(HttpConnection.java:353)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpConnectionPool.get(HttpConnectionPool.java:120)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getHttpConnection(HttpURLConnectionImpl.java:316)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.makeConnection(HttpURLConnectionImpl.java:298)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.connect(HttpURLConnectionImpl.java:236)
at org.apache.harmony.luni.internal.net.www.protocol.http.HttpURLConnectionImpl.getOutputStream(HttpURLConnectionImpl.java:645)
at com.song.cool.util.URLUtil.invokeURL(URLUtil.java:136)//對(duì)進(jìn)行耗時(shí)操作 導(dǎo)致沒(méi)用在5秒中響應(yīng)點(diǎn)擊事件
at com.song.cool.activity.WoDeJianYiActivity$1.onClick(MainActivity.java:173)//定位到ANR
at android.view.View.performClick(View.java:2535)
at android.view.View$PerformClick.run(View.java:9129)
at android.os.Handler.handleCallback(Handler.java:618)
at android.os.Handler.dispatchMessage(Handler.java:123)
at android.os.Looper.loop(SourceFile:351)
at android.app.ActivityThread.main(ActivityThread.java:3821)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:538)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:969)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:727)
at dalvik.system.NativeStart.main(Native Method)
UI卡頓
Android系統(tǒng)會(huì)每隔16ms發(fā)出信號(hào),進(jìn)行ui的渲染,如果每次渲染都成功,則用戶就可以看到流暢的畫(huà)面,為什么是16ms,因?yàn)槿藗冄劬λ芨杏X(jué)到最大幀數(shù)就是60幀,高于60幀以上人們的視覺(jué)無(wú)法感覺(jué),但是低于60fps時(shí)用戶能夠感覺(jué)到不順暢,也就是有些卡頓,所以說(shuō)16ms就是每一次渲染的最大時(shí)間.所以為了保證ui的流暢性,所以程序的大多操作都需要在16ms內(nèi)完成.
在執(zhí)行動(dòng)畫(huà)和一些listview滑動(dòng)的時(shí)候有時(shí)候會(huì)出現(xiàn)卡頓,這就說(shuō)明這部分操作很發(fā)雜,所以造成cpu或者gpu的負(fù)載過(guò)重.
造成卡頓的常見(jiàn)原因.
1.在ui線程中作輕微的耗時(shí)操作,導(dǎo)致Ui線程卡頓(輕量級(jí)的ANR)
2.布局layout過(guò)于復(fù)雜,無(wú)法在16ms內(nèi)完成渲染
3.同一時(shí)間動(dòng)畫(huà)執(zhí)行次數(shù)過(guò)多
4.View的過(guò)度繪制,導(dǎo)致同一個(gè)像素點(diǎn)繪制很多次
5.內(nèi)存頻繁的GC過(guò)多,在執(zhí)行GC時(shí)所有線程都處于暫停,導(dǎo)致暫時(shí)阻塞UI繪制(內(nèi)存抖動(dòng))
內(nèi)存抖動(dòng):
內(nèi)存抖動(dòng)是指在短時(shí)間內(nèi)創(chuàng)建大量的對(duì)象又被回收的現(xiàn)行.內(nèi)存抖動(dòng)的主要原因是頻繁的在循環(huán)當(dāng)中創(chuàng)建對(duì)象,由于頻繁創(chuàng)建對(duì)象則需要在新生代內(nèi)存區(qū)占用很大的空間,所以GC垃圾回收器就會(huì)頻繁調(diào)用,GC執(zhí)行的時(shí)候所有的線程都會(huì)掛起,如果一個(gè)幀時(shí)間內(nèi)(16ms)頻繁調(diào)用GC,那勢(shì)必會(huì)印象頁(yè)面的渲染,造成UI卡頓.
如果你在Memory Monitor里面查看到短時(shí)間發(fā)生了多次內(nèi)存的漲跌,這意味著很有可能發(fā)生了內(nèi)存抖動(dòng)。

解決UI卡頓
布局優(yōu)化,避免layout嵌套過(guò)多(盡量用GONE代替INVISIBLE,合理使用merge標(biāo)簽,在布局很復(fù)雜的時(shí)候考慮自定義控件)
列表優(yōu)化:這部分主要體現(xiàn)在ListView上,盡量復(fù)用ListView的Item,而不是每次都去填充渲染,還有就是在滑動(dòng)的時(shí)候不會(huì)加載圖片.
渲染優(yōu)化:避免對(duì)一個(gè)像素點(diǎn)的多次渲染,可以用開(kāi)發(fā)者選項(xiàng)中的現(xiàn)實(shí)過(guò)度繪制功能查看.級(jí)別有紅黃綠藍(lán).
內(nèi)存優(yōu)化:避免內(nèi)存泄露,內(nèi)存抖動(dòng)問(wèn)題,減少GC執(zhí)行次數(shù).
性能優(yōu)化:避免ANR ,避免在主線程中耗時(shí)操作
OOM內(nèi)存溢出
當(dāng)前占用的內(nèi)存的資源和申請(qǐng)的資源超出了dalvike虛擬機(jī)的最大資源就會(huì)拋出outofmemony異常.
在Android中OOM主要分為兩點(diǎn):
1.對(duì)對(duì)象的創(chuàng)建以及管理姿勢(shì)不正確導(dǎo)致的OOM,最常見(jiàn)的就是對(duì)Bitmap對(duì)象的處理.
2.內(nèi)存泄露導(dǎo)致的OOM
1.有關(guān)bitmap優(yōu)化:
1.1 圖片顯示:比如在listview滑動(dòng)的時(shí)候不去加載圖片.
1.2 及時(shí)釋放內(nèi)存:因?yàn)閎itmap的生成是由jni實(shí)現(xiàn),加載bitmap到內(nèi)存是由兩部分實(shí)現(xiàn)的:一部分java區(qū)域,一部分為native區(qū)域,java內(nèi)存區(qū)域通過(guò)GC回收.需要recycle()來(lái)釋放native的內(nèi)存.
1.3圖片壓縮:
對(duì)加載的大圖片會(huì)直接超出dalvike給分配的內(nèi)存引起OOM,所以在加載圖片之前,通過(guò)Bitmap的inSimpleSize來(lái)對(duì)圖片進(jìn)行壓縮,
1.4 inBitmap屬性:
可以復(fù)用bitmap在對(duì)內(nèi)存中占用的內(nèi)存區(qū)域.
1.5捕獲OOM異常
在實(shí)例化bitmap中對(duì)oom異常進(jìn)行捕獲,不過(guò)需要捕獲Error.
其他對(duì)象的優(yōu)化
ListView:復(fù)用convertView對(duì)象,避免每次都去填充加載一個(gè)新的view.
避免在draw中執(zhí)行創(chuàng)建的對(duì)象,由于draw方法會(huì)調(diào)用很多次,如果在其中創(chuàng)建大量的對(duì)象時(shí),會(huì)導(dǎo)致內(nèi)存急劇增加,輕則內(nèi)存抖動(dòng),嚴(yán)重的話就會(huì)造成OOM
Bitmap優(yōu)化
1.recycle:
釋放bitmap內(nèi)存的時(shí)候會(huì)釋放跟bitmap對(duì)象有關(guān)的native內(nèi)存.官方建議不必調(diào)用.
但是如果需要加載大量的圖片圖片資源,造成有大量圖片垃圾進(jìn)行回收時(shí),那必然GC需要很頻繁的調(diào)用,為了造成避免GC太頻繁造成ANR,這時(shí)可以手動(dòng)調(diào)用recycle方法,將GC任務(wù)進(jìn)行分散處理.
2.LRU算法:
存儲(chǔ)bitmap時(shí)三級(jí)緩存時(shí)使用,LRU算法是一個(gè)最近最少使用算法.內(nèi)部是一個(gè)LinkHashMap,提供了put,get方法完成了緩存的添加和獲取操作.當(dāng)緩存滿的時(shí)候,LRU算法會(huì)調(diào)trimToSize方法清楚最近最少使用的對(duì)象,并添加緩存新對(duì)象.
trimToSzie方法內(nèi)部判斷當(dāng)前大小是否判斷現(xiàn)在緩存的大小是否大于所能提供的最大緩存大小,如果大于,則循環(huán)remove最近最少使用的緩存對(duì)象,知道此大小.
3.縮略圖
由于對(duì)于大圖片來(lái)說(shuō)我們不能直接加載它到內(nèi)存,否則會(huì)引起OOM.所以需要對(duì)大圖片進(jìn)行壓縮操作,這部分操作通過(guò)inJustDecodeBounds來(lái)獲得需要加載圖片的尺寸,然后計(jì)算出圖片尺寸和控件尺寸計(jì)算出一個(gè)縮放比例,設(shè)置給inSimpleSize,這個(gè)縮放比例如果是2的話就是每有兩個(gè)像素點(diǎn),我們只取一個(gè),然后再去加載到內(nèi)存,雖然對(duì)大圖的加載是縮略的,但是由于控件的尺寸也比較小,所以在視覺(jué)方面沒(méi)有影響.
4.三級(jí)緩存
為了提高用戶體驗(yàn).對(duì)圖片的加載都需要進(jìn)行三級(jí)緩存,減少圖片的加載,減少使用流量,三級(jí)緩存分為,網(wǎng)絡(luò)緩存,本地緩存,內(nèi)存緩存.
流程: 先從內(nèi)存緩存(LRU)獲取,如果沒(méi)有從本地緩存獲取,都找不到以后再去請(qǐng)求服務(wù)器上的圖片.在圖片請(qǐng)求成功后在從將他緩存到本地和內(nèi)存中.
內(nèi)存泄露
內(nèi)存泄露是指無(wú)用對(duì)象(也就是不再使用的對(duì)象)還會(huì)被GCroot的引用鏈引用使的持續(xù)占用空間,導(dǎo)致內(nèi)存得不到釋放,導(dǎo)致的內(nèi)存空間浪費(fèi).當(dāng)內(nèi)存泄露到嚴(yán)重到一定程度時(shí)也會(huì)造成內(nèi)存溢出OOM.(可達(dá),但是無(wú)用,GC無(wú)法回收)
Android中常見(jiàn)的內(nèi)存泄露:
1.單例
由于單例的特性似的單例的對(duì)象一直由靜態(tài)變量引用保存在內(nèi)存的方法區(qū),這部分內(nèi)存的生命周期很長(zhǎng)與應(yīng)用的生命周期相同.所以很難被回收,在我們創(chuàng)建單例的時(shí)候避免傳入使單例對(duì)象引用到Activity對(duì)象,否則會(huì)造成Activity銷(xiāo)毀后無(wú)法對(duì)其占用的空間進(jìn)行回收,所以在需要Context時(shí)通過(guò)context.getApplcationContext()來(lái)獲取app的context對(duì)象而不是Activity.
2.非靜態(tài)內(nèi)部類(lèi)或非靜態(tài)匿名內(nèi)部類(lèi):
在Java中非靜態(tài)內(nèi)部類(lèi)會(huì)持有外部類(lèi)的引用,如果在內(nèi)部類(lèi)中創(chuàng)建一個(gè)靜態(tài)實(shí)例時(shí),因?yàn)殪o態(tài)實(shí)例的生命周期會(huì)和app的生命周期一樣長(zhǎng),并且該靜態(tài)實(shí)例保存這外部類(lèi)的引用,這就會(huì)導(dǎo)致外部類(lèi)對(duì)象無(wú)法的得到回收, 正確的寫(xiě)法是將非靜態(tài)內(nèi)部類(lèi)或者非靜態(tài)匿名內(nèi)部類(lèi)寫(xiě)成靜態(tài)內(nèi)部類(lèi).這樣就不會(huì)持有外部類(lèi)對(duì)象的引用.
3.Handler:
最常見(jiàn)的,原因也是因?yàn)榉庆o態(tài)內(nèi)部類(lèi)持有外部類(lèi)對(duì)象的實(shí)例子.我們長(zhǎng)在Activity或Service中通過(guò)new Handler復(fù)寫(xiě)HandMassage方法創(chuàng)建一個(gè)匿名內(nèi)部類(lèi),這時(shí)候handler匿名內(nèi)部類(lèi)就會(huì)持有外部類(lèi)的引用,在Activity銷(xiāo)毀的時(shí)候,消息隊(duì)列中還有沒(méi)處理的消息或者正在處理的消息,那么Message的target引用這handler,這是會(huì)導(dǎo)致Activity無(wú)法回收.正確寫(xiě)法也是將handler寫(xiě)成一個(gè)靜態(tài)內(nèi)部類(lèi).
4.避免使用static.
因?yàn)閟tatic的生命周期過(guò)長(zhǎng).盡量使用懶加載,如果必須用Static修飾的話,需要對(duì)他的生命周期進(jìn)行管理.
5.資源未關(guān)閉
比如socket,文檔,cursor,廣播接受者,contentProvider未關(guān)閉資源的會(huì)導(dǎo)致資源無(wú)法回收.
6.AsyncTask:
和Handler類(lèi)似,需要在Activity銷(xiāo)毀的時(shí)候調(diào)用AsyncTask的cancel方法.
內(nèi)存檢測(cè)常用工具:
1.god eyes :靜態(tài)代碼掃描,可以掃描流關(guān)閉,cursor關(guān)閉等問(wèn)題.
2.Memory Monitor:這是android studio自帶內(nèi)存分析工具 他可以只管的現(xiàn)實(shí)當(dāng)前時(shí)間內(nèi)存的使用情況,同時(shí)可以通過(guò)Java Heap生成hprof文件,通過(guò)這個(gè)文件可以顯示,無(wú)法回收的對(duì)象的引用關(guān)系.

3.Leakcanary:這是一個(gè)更直觀檢測(cè)內(nèi)存泄露的工具,只需要在項(xiàng)目中添加leakcanary的依賴(lài),并在appliction啟動(dòng)的時(shí)候進(jìn)行初始化,然后使用APP時(shí),他檢測(cè)到存在內(nèi)存泄露會(huì)發(fā)送通知,而且會(huì)說(shuō)明原因.

內(nèi)存管理
操作系統(tǒng)會(huì)對(duì)每個(gè)進(jìn)程合理的分配資源.由于android是移動(dòng)操作系統(tǒng),所以內(nèi)存資源是很有限的.google為android提供了一套特有的內(nèi)存管理機(jī)制,作為應(yīng)用的開(kāi)發(fā)者,我們必須清楚并且遵守這個(gè)機(jī)制,才能使得我們的app與android能夠和諧共存,降低被系統(tǒng)殺死的可能性.
Android對(duì)每個(gè)進(jìn)程內(nèi)存的管理:
分配機(jī)制: 在為每個(gè)進(jìn)程分配內(nèi)存的時(shí)候采用的是一個(gè)彈性的分配方式,在系統(tǒng)開(kāi)始不會(huì)為進(jìn)程分配太多的內(nèi)存,而是根據(jù)ram尺寸分配一個(gè)小額的量,當(dāng)內(nèi)存不夠使用時(shí),android會(huì)為這個(gè)進(jìn)程提供一個(gè)額外的內(nèi)存大小,但是這個(gè)大小是有限制的.
回收機(jī)制: Android是根據(jù)linux開(kāi)發(fā)的操作系統(tǒng),在內(nèi)存管理上android和linux一樣,會(huì)盡可能多的保存內(nèi)存資源,使得每次應(yīng)用重新開(kāi)啟時(shí)不再重新開(kāi)啟初始化一個(gè)進(jìn)程,從而提高了速度.
但是當(dāng)android內(nèi)存不夠使用時(shí)他就會(huì)去殺死一些進(jìn)程,在選擇殺死哪些進(jìn)程,這里有一個(gè)進(jìn)程優(yōu)先級(jí)的概念.
Android中進(jìn)程分為:前臺(tái)進(jìn)程,可見(jiàn)進(jìn)程,服務(wù)進(jìn)程,后臺(tái)進(jìn)程,空進(jìn)程
正常情況下前臺(tái)進(jìn)程,可見(jiàn)進(jìn)程,服務(wù)進(jìn)程是不會(huì)被系統(tǒng)殺死的,后臺(tái)進(jìn)程則會(huì)被系統(tǒng)存放在一個(gè)類(lèi)似LRU的緩存列表中.在回收時(shí)android還會(huì)考慮一個(gè)回收效益的問(wèn)題.就是說(shuō)android會(huì)優(yōu)先殺死一個(gè)能回收更多內(nèi)存的進(jìn)程.這樣就能做到殺死進(jìn)程越少,用戶的影響也就越小.
內(nèi)存優(yōu)化方法:
1.如果應(yīng)用占用內(nèi)存更少,則被回收的可能越小.
2.在Android內(nèi)存不足時(shí),主動(dòng)的釋放不必要的資源.通過(guò)onTrimMomey方法中釋放.
3.在合理的生命周期是保存一些數(shù)據(jù),在再次打開(kāi)應(yīng)用時(shí),系統(tǒng)可以復(fù)用這些數(shù)據(jù),而不重新創(chuàng)建.
4.避免濫用bitmap造成的內(nèi)存資源的浪費(fèi)
5.通過(guò)android提供優(yōu)化過(guò)得容器比如sparseArray和ArrayMap代替Hashmap,他們兩個(gè)底層數(shù)據(jù)結(jié)構(gòu)都是由兩個(gè)數(shù)組實(shí)現(xiàn)的,一個(gè)存放key一個(gè)存放Value.一般數(shù)據(jù)量較小是用他們替換HashMap,因?yàn)樗麄儗?duì)內(nèi)存進(jìn)行了優(yōu)化,但是在效率上沒(méi)有散列hashmap效率高.SparseArray用于key為int值時(shí),避免了自動(dòng)拆裝箱,其他類(lèi)型的key用ArrayMap.
6.使用多進(jìn)程,對(duì)于對(duì)內(nèi)存較多的模塊和長(zhǎng)期運(yùn)行在后臺(tái)的模塊為其開(kāi)辟一個(gè)新進(jìn)程,比如定位和推送一般都自己獨(dú)占一個(gè)進(jìn)程.但是要考慮清楚解決多進(jìn)程之間數(shù)據(jù)傳輸以及安全的問(wèn)題.