Android性能優(yōu)化
我們都知道Android 應(yīng)用是很消耗內(nèi)存的 我們應(yīng)該如果避免以及提高對Android 性能的優(yōu)化呢?
我們首先可以從以下幾個(gè)方面開始:
內(nèi)存優(yōu)化 、布局 ,電量、 流量 啟動(dòng)
首先來說一下眾所周知的 內(nèi)存泄漏 內(nèi)存溢出問題
1.內(nèi)存泄漏:
內(nèi)存泄漏 了解一下小編的:Android內(nèi)存優(yōu)化——常見內(nèi)存泄露及優(yōu)化方案
內(nèi)存抖動(dòng):
2.內(nèi)存抖動(dòng)
什么是內(nèi)存抖動(dòng)呢?Android里內(nèi)存抖動(dòng)是指內(nèi)存頻繁地分配和回收,而頻繁的gc會(huì)導(dǎo)致卡頓,嚴(yán)重時(shí)還會(huì)導(dǎo)致OOM。
一個(gè)很經(jīng)典的案例是string拼接創(chuàng)建大量小的對象(比如在一些頻繁調(diào)用的地方打字符串拼接的log的時(shí)候)。
而內(nèi)存抖動(dòng)為什么會(huì)引起OOM呢?
主要原因還是有因?yàn)榇罅啃〉膶ο箢l繁創(chuàng)建,導(dǎo)致內(nèi)存碎片,從而當(dāng)需要分配內(nèi)存時(shí),雖然總體上還是有剩余內(nèi)存可分配,而由于這些內(nèi)存不連續(xù),導(dǎo)致無法分配,系統(tǒng)直接就返回OOM了。
比如我們坐地鐵的時(shí)候,假設(shè)你沒帶公交卡去坐地鐵,地鐵的售票機(jī)就只支持5元,10元,而哪怕你這個(gè)時(shí)候身上有1萬張1塊的都沒用(是不是覺得很反人類..)。當(dāng)然你可以去兌換5元,10元,而在Android系統(tǒng)里就沒那么幸運(yùn)了,系統(tǒng)會(huì)直接拒絕為你分配內(nèi)存,并扔一個(gè)OOM給你(有人說Android系統(tǒng)并不會(huì)對Heap中空閑內(nèi)存區(qū)域做碎片整理,待驗(yàn)證)。
3. 圖片壓縮
BitmapFactory 在解碼圖片時(shí),可以帶一個(gè)Options,有一些比較有用的功能,比如:
inTargetDensity 表示要被畫出來時(shí)的目標(biāo)像素密度
inSampleSize 這個(gè)值是一個(gè)int,當(dāng)它小于1的時(shí)候,將會(huì)被當(dāng)做1處理,如果大于1,那么就會(huì)按照比例(1 / inSampleSize)縮小bitmap的寬和高、降低分辨率,大于1時(shí)這個(gè)值將會(huì)被處置為2的倍數(shù)。例如,width=100,height=100,inSampleSize=2,那么就會(huì)將bitmap處理為,width=50,height=50,寬高降為1 / 2,像素?cái)?shù)降為1 / 4
inJustDecodeBounds 字面意思就可以理解就是只解析圖片的邊界,有時(shí)如果只是為了獲取圖片的大小就可以用這個(gè),而不必直接加載整張圖片。
inPreferredConfig 默認(rèn)會(huì)使用ARGB_8888,在這個(gè)模式下一個(gè)像素點(diǎn)將會(huì)占用4個(gè)byte,而對一些沒有透明度要求或者圖片質(zhì)量要求不高的圖片,可以使用RGB_565,一個(gè)像素只會(huì)占用2個(gè)byte,一下可以省下50%內(nèi)存。
inPurgeable和inInputShareable 這兩個(gè)需要一起使用,BitmapFactory.java的源碼里面有注釋,大致意思是表示在系統(tǒng)內(nèi)存不足時(shí)是否可以回收這個(gè)bitmap,有點(diǎn)類似軟引用,但是實(shí)際在5.0以后這兩個(gè)屬性已經(jīng)被忽略,因?yàn)橄到y(tǒng)認(rèn)為回收后再解碼實(shí)際會(huì)反而可能導(dǎo)致性能問題
inBitmap 官方推薦使用的參數(shù),表示重復(fù)利用圖片內(nèi)存,減少內(nèi)存分配,在4.4以前只有相同大小的圖片內(nèi)存區(qū)域可以復(fù)用,4.4以后只要原有的圖片比將要解碼的圖片大既可以復(fù)用了。
4. 緩存池大小
現(xiàn)在很多圖片加載組件都不僅僅是使用軟引用或者弱引用了,實(shí)際上類似Glide 默認(rèn)使用的事LruCache,因?yàn)檐浺?弱引用都比較難以控制,使用LruCache可以實(shí)現(xiàn)比較精細(xì)的控制,而默認(rèn)緩存池設(shè)置太大了會(huì)導(dǎo)致浪費(fèi)內(nèi)存,設(shè)置小了又會(huì)導(dǎo)致圖片經(jīng)常被回收,所以需要根據(jù)每個(gè)App的情況,以及設(shè)備的分辨率,內(nèi)存計(jì)算出一個(gè)比較合理的初始值,可以參考Glide的做法。
5. MAT 內(nèi)存分析工具
4. LeakCanary
2.LeakCanary
第一步使用LeakCanary 我們需要先導(dǎo)入兩個(gè)依賴
debugImplementation'com.squareup.leakcanary:leakcanary-android:1.5.4'
releaseImplementation'com.squareup.leakcanary:leakcanary-android-no-op:1.5.4'

第二步在我們自定義繼承Application類的onCreate()方法里面

如果需要具體的檢查內(nèi)存泄漏的時(shí)候:

完成以上操作之后會(huì)在我們的手機(jī)或者模擬器上生成此應(yīng)用

LeakCanary會(huì)找到并修復(fù) 多個(gè)內(nèi)存泄漏問題 將OOM崩潰的幾率降低94%。
詳解:
https://blog.csdn.net/qq_20280683/article/details/77964208
https://www.cnblogs.com/fuyaozhishang/p/7753013.html
7. 通過Android Studio 自帶的工具 Android Profilter 找到MEMORY這個(gè)欄目 進(jìn)行檢測

電量優(yōu)化
Android應(yīng)用開發(fā)中的網(wǎng)絡(luò)、定位、傳感器等都是比較耗電的特性,我們應(yīng)該正確使用API來有效降低應(yīng)用的耗電量。
1.BroadcastReceiver:
在代碼實(shí)現(xiàn)中需要盡量避免無用操作代碼的執(zhí)行,減少應(yīng)用損耗的電量。
對于BroadcastReceiver,通常的做法是在界面onPasuse之后取消廣播監(jiān)聽器的監(jiān)聽操作,同時(shí)根據(jù)具體業(yè)務(wù)需求選擇當(dāng)應(yīng)用位于后臺(tái)時(shí)是否禁用廣播接收器。
2.數(shù)據(jù)傳輸:
數(shù)據(jù)傳輸方式:藍(lán)牙傳輸,Wi-Fi傳輸,移動(dòng)網(wǎng)絡(luò)傳輸?shù)取?br>
優(yōu)化:
后臺(tái)數(shù)據(jù)傳輸?shù)墓芾恚焊鶕?jù)具體業(yè)務(wù)需求,嚴(yán)格限制應(yīng)用位于后臺(tái)時(shí)是否禁用某些數(shù)據(jù)傳輸,盡量能夠避免無效的數(shù)據(jù)傳輸。
數(shù)據(jù)傳輸?shù)念l度問題:通過經(jīng)驗(yàn)值或者數(shù)據(jù)統(tǒng)計(jì)的方法確定好數(shù)據(jù)傳輸?shù)念l度,避免冗余重復(fù)的數(shù)據(jù)傳輸,數(shù)據(jù)傳輸過程中要壓縮數(shù)據(jù)大小,合并網(wǎng)絡(luò)請求,避免輪詢等。
3.位置服務(wù):
三種位置服務(wù):
GPS定位:通過接收全球定位系統(tǒng)的衛(wèi)星提供的經(jīng)緯度坐標(biāo)信息實(shí)現(xiàn)位置服務(wù),精度是最高的,通常在10米以內(nèi),在時(shí)間和電量的消耗上也是最高的。
網(wǎng)絡(luò)定位:通過移動(dòng)通信的基站信號(hào)差異來計(jì)算出手機(jī)所在的位置,精度比GPS定位差很多,通常在幾百米范圍內(nèi)。
被動(dòng)定位:最省電的定位服務(wù),如果應(yīng)用使用被動(dòng)定位服務(wù),這個(gè)應(yīng)用會(huì)等待手機(jī)中其他應(yīng)用、服務(wù)或者系統(tǒng)組件發(fā)出定位請求,并和這些組件的監(jiān)聽器一起接收位置更新。
正確有限地使用位置服務(wù)器,減少應(yīng)用耗電量。所以在代碼中使用位置服務(wù)時(shí),需要注意:
有沒有及時(shí)注銷位置監(jiān)聽器:長時(shí)間的監(jiān)聽位置更新會(huì)耗費(fèi)大量的電量,通過可以選擇在頁面的onPasuse中進(jìn)行注銷操作,更好用且全局有效的做法是禁用位置監(jiān)聽器。
位置更新監(jiān)聽頻率的設(shè)定:根據(jù)具體的業(yè)務(wù)需求設(shè)置一個(gè)合適的更新頻率值,通常需要在定位精度和耗電量之間綜合考慮。
多種位置服務(wù)的選擇:綜合考慮應(yīng)用的具體需求在不同時(shí)機(jī)采用不同的定位服務(wù)或者選擇第三方的定位SDK。
4.AlarmManager:
AlarmManager的喚醒操作是比較耗電的,通常情況下需要保證兩次喚醒操作的時(shí)間間隔不要太短,在不需要使用喚醒功能的情況下盡早取消AlarmManager,否則應(yīng)用會(huì)一直處于耗電狀態(tài)。
5.WakeLock:
使用WakeLock時(shí),需要切記及時(shí)釋放鎖,而且通常情況下,要盡早地釋放WakeLock。
布局優(yōu)化
在Android開發(fā)時(shí),如果創(chuàng)建的布局層次結(jié)構(gòu)比較復(fù)雜,View樹嵌套的層次比較深,會(huì)使頁面展現(xiàn)的事件比較長,導(dǎo)致應(yīng)用運(yùn)行起來越來越慢,所以需要進(jìn)行布局優(yōu)化。
1.include標(biāo)簽共享布局:
將通用的布局抽取出來,獨(dú)立成一個(gè)XML文件,在需要用到的頁面中使用include標(biāo)簽引入進(jìn)來,減少代碼量,便于修改。
2.ViewStub標(biāo)簽實(shí)現(xiàn)延遲加載:
ViewStub是一種不可視并且大小為0的視圖,可以延遲到運(yùn)行時(shí)才填充布局資源。當(dāng)ViewStub設(shè)置為可見或者被inflate之后,會(huì)填充布局資源,ViewStub會(huì)被填充的視圖代替,和普通的視圖沒有區(qū)別。
ViewStub在需要顯示的時(shí)候才會(huì)進(jìn)行視圖的填充,實(shí)現(xiàn)延遲加載的目的。
3.merge標(biāo)簽減少布局層次:
當(dāng)一個(gè)獨(dú)立的布局文件最外層是FrameLayout且這個(gè)布局不需要設(shè)置背景等屬性時(shí)或者當(dāng)前布局是另外一個(gè)布局的子布局時(shí),可以使用merge來減少布局的層次。
4.盡量使用CompoundDrawable:
在LinearLayout布局中,如果存在相鄰的ImageView和TextView,可以使用compound drawable合二為一成為一個(gè)TextView,ImageView中的圖片變成TextVIew的drawableTop/drawableLeft/drawableRight/ddrawableBottom屬性,之間的間隔使用drawablePadding屬性來代替。
5.使用Lint:
Lint也可以用來檢查應(yīng)用的布局是否存在可優(yōu)化的地方,為優(yōu)化布局設(shè)置的規(guī)則如下:
AndroidLintUseCompoundDrawables:盡量使用CompoundDrawable。
MergeRootFrame:使用merge標(biāo)簽減少布局層次。
TooManyViews:單個(gè)布局中存在太多的View,默認(rèn)情況下,單個(gè)布局中View的個(gè)數(shù)最多只能是80個(gè),可以考慮使用CompoundDdrawables等來減少View的個(gè)數(shù)。
TooDeepLayout:避免過深的布局嵌套,默認(rèn)情況下,單個(gè)布局中最多層級(jí)是10,可以考慮使用RelativeLayout來減少布局的層次。
UselessParent:當(dāng)一個(gè)布局不是一個(gè)SrcollView或者根布局,只有一個(gè)子View且沒有設(shè)置背景時(shí)可以將它移除掉,并將它的子View移動(dòng)到它的父容器中,得到更扁平的布局層次。
NestedWeights:android:layout_weight屬性會(huì)使得View控件被測量兩次,當(dāng)一個(gè)LinearLayout擁有非0dp值的android:layout_weight屬性,這時(shí)如果將它嵌套在兩一個(gè)擁有非0dp的android:layout_weight的LinearLayout,這時(shí)測量的次數(shù)將呈指數(shù)級(jí)別增加。
UselessLeaf:一個(gè)布局如果沒有子View也沒有設(shè)置背景,通??梢砸瞥?,可以得到更扁平和高效的布局層次。
InefficientWeight:當(dāng)LinearLayout中只有一個(gè)子View定義了android:layout_weight屬性,更高性能的做法是使用0dp的android:layout_height或者android:layout_weidth來替換它,這個(gè)子View就不需要測量它自身對應(yīng)的大小。
網(wǎng)絡(luò)優(yōu)化
網(wǎng)絡(luò)優(yōu)化可以節(jié)省網(wǎng)絡(luò)流量,節(jié)省電量,提高應(yīng)用的響應(yīng)。
1.避免DNS解析:
DNS是域名系統(tǒng),根據(jù)應(yīng)用請求所用的域名URL去網(wǎng)絡(luò)映射表中查找對應(yīng)的IP地址,這個(gè)過程可能會(huì)需要上百毫秒的時(shí)間,可能會(huì)存在DNS劫持的危險(xiǎn)。所有根據(jù)具體的業(yè)務(wù)需求,可以采用增加動(dòng)態(tài)更新能力的IP方式,或者在IP方式訪問失敗時(shí)切換到域名訪問方式。
2.合并網(wǎng)絡(luò)請求:
對于網(wǎng)絡(luò)請求應(yīng)該盡量減少請求的接口,能夠合并的網(wǎng)絡(luò)請求就盡量合并。
3.預(yù)先獲取數(shù)據(jù):
預(yù)先獲取數(shù)據(jù)能夠?qū)⒕W(wǎng)絡(luò)請求集中在一次,其他時(shí)間段手機(jī)就可以切換到空閑狀態(tài),避免經(jīng)常性的喚醒和空閑,起到節(jié)省電量的作用。
4.避免輪詢:
輪詢是指客戶端每隔一段時(shí)間就向服務(wù)端主動(dòng)發(fā)起的網(wǎng)絡(luò)請求,存在需要的數(shù)據(jù)就拉取,沒有就等待下一次輪詢。一般情況下能使用推送替換的盡量使用推送,避免使用Thread.sleep()函數(shù)循環(huán)等待,可以使用系統(tǒng)AlarmMananger實(shí)現(xiàn)定時(shí)輪詢。
5.優(yōu)化重連機(jī)制:
盡量避免網(wǎng)絡(luò)請求失敗時(shí),無限制循環(huán)重試連接,可以設(shè)定一個(gè)最大重連次數(shù),超過次數(shù)限制之后結(jié)束重連,等一段時(shí)間后再嘗試連接。
6.離線緩存:
對于圖片,文件等數(shù)據(jù),可以使用二級(jí)緩存策略,當(dāng)緩存中有對應(yīng)的圖片或者文件時(shí),可以直接從緩存中讀取,不需要網(wǎng)絡(luò)請求,避免網(wǎng)絡(luò)延遲,節(jié)省流量。
7.壓縮數(shù)據(jù)大?。?br> 對于客戶端來說,可以對發(fā)送給服務(wù)器的數(shù)據(jù)進(jìn)行g(shù)zip壓縮,同時(shí)可以選用更優(yōu)的數(shù)據(jù)傳輸格式來減少網(wǎng)絡(luò)上面?zhèn)鬏數(shù)臄?shù)據(jù)。
8.不同的網(wǎng)絡(luò)環(huán)境使用不同的超時(shí)策略:
可以通過監(jiān)聽
ConnectivityMananger.CONNECTIVITY_ACTION的變化來獲取最新的網(wǎng)絡(luò)類型,動(dòng)態(tài)調(diào)整網(wǎng)絡(luò)超時(shí)時(shí)間。
9.CDN的使用:
CDN,內(nèi)容發(fā)布網(wǎng)絡(luò),盡可能避免網(wǎng)絡(luò)上可能影響數(shù)據(jù)傳輸速度和穩(wěn)定性的環(huán)節(jié),實(shí)現(xiàn)更快,更穩(wěn)定的數(shù)據(jù)傳輸,其中CDN加速能夠緩解電信核心網(wǎng)絡(luò)延遲帶來的影響。