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