當(dāng)出現(xiàn)App啟動(dòng)慢、界面跳轉(zhuǎn)慢、事件相應(yīng)慢、滑動(dòng)和動(dòng)畫卡頓、展現(xiàn)內(nèi)容慢等問(wèn)題的時(shí)候意味著App性能出現(xiàn)問(wèn)題,這個(gè)時(shí)候就有必要對(duì)App做一下性能優(yōu)化,其實(shí)不是非等到App出現(xiàn)性能問(wèn)題的時(shí)候才開(kāi)始做優(yōu)化,而是要把優(yōu)化做到平時(shí)的開(kāi)發(fā)和維護(hù)過(guò)程中。但性能優(yōu)化如何下手?下面結(jié)合Android官方文檔以及平時(shí)開(kāi)發(fā)中的經(jīng)驗(yàn)來(lái)聊聊Andrid性能優(yōu)化的技巧。
Layout布局優(yōu)化
Layout的優(yōu)化目的是處理應(yīng)用UI卡頓、ANR問(wèn)題,使App用起來(lái)絲般順滑。
- 優(yōu)化布局結(jié)構(gòu),避免復(fù)雜的Layout嵌套。比如使用<code>merge</code> 標(biāo)簽減少布局嵌套。
使用 Hierarchy Viewer 分析布局,對(duì)出現(xiàn)紅色、或者黃色的布局需要尤為注意。
Tree View - 使用Lint進(jìn)行資源及冗余UI布局分析,根據(jù)Lint report提示做相應(yīng)的優(yōu)化
- 使用<code>include</code> 標(biāo)簽達(dá)到布局重用的目的,
- 使用懶加載的Views,比如ViewStub
- 使用Memory監(jiān)測(cè)及GC打印與Allocation Tracker進(jìn)行UI卡頓分析。
- 使用RelativeLayout扁平化布局,ListView item布局,一定要扁平化。
- TextView組合圖標(biāo),代替LinearLayout+TextView+ImageView。
- 在項(xiàng)目中使用StrictMode來(lái)檢查主線程耗時(shí)操作等。
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().penaltyDialog().build());
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder().detectAll().penaltyLog().build());
- 自定義View時(shí),避免onDraw方法過(guò)度繪制或者重復(fù)繪制,避免在里面初始化變量等。
- ListView或者RecyclerView渲染item時(shí),將耗時(shí)操作(文件,網(wǎng)絡(luò), DB)放在子線程中,item要是用ViewHolder布局緩存復(fù)用機(jī)制。
- 使用traces.txt文件進(jìn)行ANR分析優(yōu)化,或者使用Blockcanary,使用方法和Leackcanary類似。
- 使用SVG代替圖片
- 使用xml代替圖片
優(yōu)化設(shè)備的電池壽命
電池優(yōu)化一般只針對(duì)耗電量比較大的App,一般應(yīng)用貌似都還沒(méi)有考慮到這一點(diǎn)。對(duì)于電池使用優(yōu)化主要考慮:
- 監(jiān)控電池電量和充電狀態(tài),監(jiān)控顯著的電池電量變化,一般而言,最好在電池電量極低時(shí)停用所有后臺(tái)更新。
- 確定和監(jiān)控插接狀態(tài)和基座類型,具體參考官方文檔
- 確定和監(jiān)控網(wǎng)絡(luò)連接狀態(tài),可以利用 ConnectivityManager 來(lái)檢查是否已實(shí)際連入互聯(lián)網(wǎng)以及已連入情況下的連接類型。
高效地利用線程
為了加快響應(yīng)速度,需要把費(fèi)時(shí)的操作(比如網(wǎng)絡(luò)請(qǐng)求、數(shù)據(jù)庫(kù)操作或者復(fù)雜的計(jì)算)從主線程移動(dòng)到一個(gè)單獨(dú)的線程中。
- 可以使用自定義Thread,AsyncTask或者IntentService來(lái)創(chuàng)建后臺(tái)操作。
- 可以使用線程池,并用創(chuàng)建一個(gè)Manager類對(duì)多線程統(tǒng)一管理
private PhotoManager() {
...
// Sets the amount of time an idle thread waits before terminating
private static final int KEEP_ALIVE_TIME = 1;
// Sets the Time Unit to seconds
private static final TimeUnit KEEP_ALIVE_TIME_UNIT = TimeUnit.SECONDS;
// Creates a thread pool manager
mDecodeThreadPool = new ThreadPoolExecutor(
NUMBER_OF_CORES, // Initial pool size
NUMBER_OF_CORES, // Max pool size
KEEP_ALIVE_TIME,
KEEP_ALIVE_TIME_UNIT,
mDecodeWorkQueue);
}
- 當(dāng)View銷毀或者不可見(jiàn)的情況下,線程有必要取消、停止, 通常在線程內(nèi)部設(shè)置flag取消耗時(shí)操作,盡快讓線程結(jié)束。
/*
* Before continuing, checks to see that the Thread hasn't
* been interrupted
*/
if (Thread.interrupted()) {
return;
}
/*
* isCanceled標(biāo)志
*/
if (isCanceled()) {
return;
}
優(yōu)化網(wǎng)絡(luò)
- 如果沒(méi)有網(wǎng)絡(luò)連接,請(qǐng)讓你的應(yīng)用跳過(guò)網(wǎng)絡(luò)操作;只在有網(wǎng)絡(luò)連接并且無(wú)漫游的情況下更新數(shù)據(jù);
- 選擇兼容的數(shù)據(jù)格式,把含有文本數(shù)據(jù)和二進(jìn)制數(shù)據(jù)的請(qǐng)求全部轉(zhuǎn)化成二進(jìn)制數(shù)據(jù)格式請(qǐng)求;
- 使用高效的轉(zhuǎn)換工具,多考慮使用流式轉(zhuǎn)換工具,少用樹(shù)形的轉(zhuǎn)換工具;
- 為了更快的用戶體驗(yàn),請(qǐng)減少重復(fù)訪問(wèn)服務(wù)器的操作;
- 如果可以的話,請(qǐng)使用framework的GZIP庫(kù)來(lái)壓縮文本數(shù)據(jù)以高效使用CPU資源。
目前App針對(duì)網(wǎng)絡(luò)的優(yōu)化,主要使用一個(gè)好的網(wǎng)絡(luò)下載框架,目前OkHttp + Retrofit基本成為了標(biāo)配。
內(nèi)存泄露分析工具
- 使用AS的Memory monitor,平時(shí)用來(lái)直觀了解自己應(yīng)用的全局內(nèi)存情況,大的泄露才能有感知。
- DDMS-Heap內(nèi)存監(jiān)測(cè)工具 同上,大的泄露才能有感知。
- MAT工具全稱為Memory Analyzer Tool,詳細(xì)分析Java堆內(nèi)存的工具,該工具非常強(qiáng)大。
- Leakcanary神器,比較強(qiáng)大,可以感知泄露且定位泄露;實(shí)質(zhì)是MAT原理,只是更加自動(dòng)化了,當(dāng)現(xiàn)有代碼量已經(jīng)龐大成型,且無(wú)法很快察覺(jué)掌控全局代碼時(shí)極力推薦;或者是偶現(xiàn)泄露的情況下極力推薦。
規(guī)避內(nèi)存泄露建議
- Context使用不當(dāng)造成內(nèi)存泄露;不要對(duì)一個(gè)Activity Context保持長(zhǎng)生命周期的引用(譬如上面概念部分給出的示例)。盡量在一切可以使用應(yīng)用ApplicationContext代替Context的地方進(jìn)行替換(原理我前面有一篇關(guān)于Context的文章有解釋)。
- 非靜態(tài)內(nèi)部類的靜態(tài)實(shí)例容易造成內(nèi)存泄漏;即一個(gè)類中如果你不能夠控制它其中內(nèi)部類的生命周期(譬如Activity中的一些特殊Handler等),則盡量使用靜態(tài)類和弱引用來(lái)處理(譬如ViewRoot的實(shí)現(xiàn))。
- 警惕線程未終止造成的內(nèi)存泄露;譬如在Activity中關(guān)聯(lián)了一個(gè)生命周期超過(guò)Activity的Thread,在退出Activity時(shí)切記結(jié)束線程。一個(gè)典型的例子就是HandlerThread的run方法是一個(gè)死循環(huán),它不會(huì)自己結(jié)束,線程的生命周期超過(guò)了Activity生命周期,我們必須手動(dòng)在Activity的銷毀方法中中調(diào)運(yùn)thread.getLooper().quit();才不會(huì)泄露。
- 對(duì)象的注冊(cè)與反注冊(cè)沒(méi)有成對(duì)出現(xiàn)造成的內(nèi)存泄露;譬如注冊(cè)廣播接收器、注冊(cè)觀察者(典型的譬如數(shù)據(jù)庫(kù)的監(jiān)聽(tīng))等。
- 創(chuàng)建與關(guān)閉沒(méi)有成對(duì)出現(xiàn)造成的泄露;譬如Cursor資源必須手動(dòng)關(guān)閉,WebView必須手動(dòng)銷毀,流等對(duì)象必須手動(dòng)關(guān)閉等。
- 不要在執(zhí)行頻率很高的方法或者循環(huán)中創(chuàng)建對(duì)象,可以使用HashTable等創(chuàng)建一組對(duì)象容器從容器中取那些對(duì)象,而不用每次new與釋放。
- 避免代碼設(shè)計(jì)模式的錯(cuò)誤造成內(nèi)存泄露;譬如循環(huán)引用,A持有B,B持有C,C持有A,這樣的設(shè)計(jì)誰(shuí)都得不到釋放。
代碼規(guī)范制定并遵守
一致的代碼風(fēng)格,有利于代碼維護(hù)、查看和發(fā)現(xiàn)問(wèn)題所在。參考CodingStyle
其他
- 使用Lint、 Proguard工具給APK包減肥。
- 開(kāi)啟GPU過(guò)度繪制調(diào)試,粉紅色盡量?jī)?yōu)化,界面盡量保持藍(lán)綠顏色, 紅色肯定是有問(wèn)題的,不能忍受。
- 合理使用數(shù)據(jù)類型,比如StringBuilder代替String,少用父類聲明(List, Map), 盡量使用Android提供的數(shù)據(jù)結(jié)構(gòu)例如SparseArray。
- 少用枚舉enum,可以使用static final的變量或者intDef 注解代替。
- bitmap 必要時(shí)進(jìn)行壓縮,降低分辨率處理,并且使用完了記得recycle。
- 使用LruCache最Memory或者Disk Cache
- OnTrimMemory 方法監(jiān)聽(tīng),收到內(nèi)存報(bào)警,及時(shí)釋放內(nèi)存。
- 你要知道for loop中不要聲明臨時(shí)變量,不到萬(wàn)不得已不要在里面寫try catch.
- 設(shè)計(jì)模式合理的使用,不要一味的為了設(shè)計(jì)模式而過(guò)分的抽象代碼,因?yàn)榇a抽象系數(shù)與代碼加載執(zhí)行時(shí)間成正比
- Handler發(fā)送消息時(shí)盡量使用obtain去獲取已經(jīng)存在的Message對(duì)象進(jìn)行復(fù)用,而不是新new Message對(duì)象,這樣可以減輕內(nèi)存壓力。
- 使用Parcelable代替Serializable,Parcelable更適合Android。
寫在最后
性能優(yōu)化是一個(gè)大的話題,設(shè)計(jì)到的知識(shí)太多太多,上面只是提到部分,后續(xù)會(huì)不但的更新,如有不對(duì)之處,還望指正。
