Android設(shè)備作為一種移動(dòng)設(shè)備,不管是內(nèi)存還是CPU的性能都受到了一定的限制,無法做到像PC設(shè)備那樣具有超大的內(nèi)存和高性能的CPU,這也意味著Android程序不可能無限制地使用內(nèi)存和CPU資源,過多地使用內(nèi)存會(huì)導(dǎo)致程序內(nèi)存溢出,即OOM。而過多地使用CPU資源,一般指做大量的耗時(shí)任務(wù),會(huì)導(dǎo)致手機(jī)變得卡頓甚至出現(xiàn)無法響應(yīng)的情況,即ANR。
Android的性能優(yōu)化方法
1、布局優(yōu)化
布局優(yōu)化的思想很簡單,就是盡量減少布局文件的層級(jí),布局中的層級(jí)少了,這就意味著Android繪制時(shí)的工作量少了,那么程序的性能自然就高了。
那么如何進(jìn)行布局優(yōu)化呢?有以下兩點(diǎn):
? 首先刪除布局中無用的看控件和層級(jí),其次有選擇地使用性能較低的ViewGroup,比如RelativeLayout。
? 可以采用標(biāo)簽、標(biāo)簽、ViewStub。標(biāo)簽主要用于布局重用,標(biāo)簽一般配合標(biāo)簽使用,它可以降低減少布局的層級(jí),而ViewStub則提供了按需加載的功能。
2、繪制優(yōu)化
繪制優(yōu)化是指View的onDraw方法要避免執(zhí)行大量的操作,主要體現(xiàn)在兩個(gè)方面:
? onDraw中不要?jiǎng)?chuàng)建新的局部對(duì)象,這是因?yàn)閛nDraw方法可能會(huì)被頻繁調(diào)用,這樣就會(huì)在一瞬間產(chǎn)生大量的臨時(shí)對(duì)象,這不僅占用了過多的內(nèi)存而且還會(huì)導(dǎo)致系統(tǒng)會(huì)更頻繁gc,降低程序的執(zhí)行效率。
? onDraw方法中不要做耗時(shí)的任務(wù),也不能執(zhí)行成千上萬次的循環(huán)操作,盡管每次循環(huán)都很輕量級(jí),但是大量的循環(huán)仍然十分搶占CPU的時(shí)間片,這會(huì)造成View的繪制過程很不流暢。
3、內(nèi)存優(yōu)化
內(nèi)存泄露在開發(fā)過程中是一個(gè)需要重視的問題,內(nèi)存優(yōu)化分為兩個(gè)方面,一方面是在開發(fā)過程中避免寫出有內(nèi)存泄露的代碼,另一方面是通過一些分析工具比如MAT來找出潛在的內(nèi)存泄露繼而解決。
場(chǎng)景1:靜態(tài)變量導(dǎo)致內(nèi)存泄露
比如下面這段代碼:
public class MainActivity extends Activity { private static final String TAG = "MainActivity"; private static Context sContext; private static View sView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); sContext = this; sView = new View(this); } }
MainActivity無法正常銷毀,因?yàn)殪o態(tài)變量sContext引用了它。同樣,sView是一個(gè)靜態(tài)變量,他內(nèi)部持有了當(dāng)前Activity,所以Activity仍然無法釋放。
場(chǎng)景2:單例模式導(dǎo)致內(nèi)存泄露
靜態(tài)變量導(dǎo)致的內(nèi)存泄露都太過明顯了,但單例模式所帶來的內(nèi)存泄露是我們?nèi)菀缀鲆暤摹1热缦旅孢@段代碼:
public class TestManager { private List mOnDataArrivedListeners = new ArrayList(); private static class SingletonHolder { public static final TestManager INSTANCE = new TestManager(); } private TestManager() { } public static TestManager getInstance() { return SingletonHolder.INSTANCE; } public synchronized void registerListener(OnDataArrivedListener listener) { if (!mOnDataArrivedListeners.contains(listener)) { mOnDataArrivedListeners.add(listener); } } public synchronized void unregisterListener(OnDataArrivedListener listener) { mOnDataArrivedListeners.remove(listener); } public interface OnDataArrivedListener { public void onDataArrived(Object data); } }
首先提供一個(gè)單例模式的TestManager,TestManager可以接收外部的注冊(cè)并將外部的監(jiān)聽器存儲(chǔ)起來。然后用Activity實(shí)現(xiàn)OnDataArrivedListener接口并向TestManager注冊(cè)監(jiān)聽,但是如果缺少解注冊(cè)的操作,會(huì)引起內(nèi)存泄露。比如下面這段代碼:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TestManager.getInstance().registerListener(this); }
Activity的對(duì)象被單例模式的TestManager所持有,而單例模式的特點(diǎn)是其生命周期和Application保持一致,因此Activity對(duì)象無法被及時(shí)釋放。
場(chǎng)景3:屬性動(dòng)畫導(dǎo)致的內(nèi)存泄露
從Android3.0開始,Google提供了屬性動(dòng)畫,屬性動(dòng)畫中有這么一類無限循環(huán)的動(dòng)畫,如果在Activity中播放此類動(dòng)畫且沒有在onDestroy中停止動(dòng)畫,那么動(dòng)畫就會(huì)一直播放下去,盡管已經(jīng)無法在界面上看到動(dòng)畫效果,但這個(gè)時(shí)候Activity的View會(huì)被動(dòng)畫持有,而View又持有了Activity,最終Activity無法釋放。
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ObjectAnimator animator = ObjectAnimator.ofFloat(mButton, "rotation",0, 360).setDuration(2000); animator.setRepeatCount(ValueAnimator.INFINITE); animator.start(); //animator.cancel(); }
4、響應(yīng)速度優(yōu)化和ANR日志分析
響應(yīng)速度優(yōu)化的核心思想是避免在主線程中做耗時(shí)操作,但是有時(shí)候的確有很多耗時(shí)操作,怎么辦呢?可以將這些耗時(shí)操作放在線程中去執(zhí)行,即采用異步的方式執(zhí)行耗時(shí)操作。響應(yīng)速度過慢更多地體現(xiàn)在Activity的啟動(dòng)速度上面,如果在主線程中做太多的事情,會(huì)導(dǎo)致Activity啟動(dòng)出現(xiàn)黑屏現(xiàn)象,甚至出現(xiàn)ANR。Android規(guī)定,Activity如果5秒鐘之內(nèi)無法響應(yīng)屏幕觸摸事件或者鍵盤輸入事件就會(huì)出現(xiàn)ANR,而BroadcastReceiver如果10秒之內(nèi)還未執(zhí)行完操作也會(huì)出現(xiàn)ANR,那么在實(shí)際開發(fā)過程中遇到ANR,怎么定位問題呢?其實(shí)當(dāng)一個(gè)進(jìn)程發(fā)生ANR了以后,系統(tǒng)會(huì)在/data/anr/目錄下創(chuàng)建一個(gè)文件traces.txt,通過分析這個(gè)文件就能定位出ANR的原因。比如下面代碼在Activity的onCreate中休眠30s,程序運(yùn)行持續(xù)點(diǎn)擊屏幕,應(yīng)用一定會(huì)出現(xiàn)ANR:
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); SystemClock.sleep(30 * 1000); }
5、ListView和Bitmap優(yōu)化
ListView優(yōu)化三個(gè)方面:
? 采用ViewHolder并避免在getView中執(zhí)行耗時(shí)操作
? 根據(jù)列表的滑動(dòng)狀態(tài)來控制任務(wù)的執(zhí)行頻率,比如當(dāng)列表快速滑動(dòng)時(shí)顯然是不太適合開啟大量異步任務(wù)的。
? 嘗試開啟硬件加速來使ListView的滑動(dòng)更加流暢。
Bitmap優(yōu)化,主要是通過BitmapFactory.Options來根據(jù)需要對(duì)圖片進(jìn)行采樣,采樣過程中主要用到了BitmapFactory.Option的inSampleSize參數(shù)。
6、線程優(yōu)化
線程優(yōu)化的思想是采用線程池,避免在程序中存在大量的Thread。線程池可以重用內(nèi)部的線程,從而避免了現(xiàn)場(chǎng)的創(chuàng)建和銷毀所帶來的性能開銷,同時(shí)線程池還能有效地控制線程池的最大并發(fā)數(shù),避免大量的線程因互相搶占系統(tǒng)資源從而導(dǎo)致阻塞現(xiàn)象發(fā)生。
7、其他性能優(yōu)化建議
還有一些其他性能優(yōu)化的小建議,通過它們可以在一定程度上提高性能:
? 避免創(chuàng)建過多的對(duì)象
? 不要過多使用枚舉,枚舉占用的內(nèi)存空間要比整型大
? 常量請(qǐng)用static final來修飾
? 使用一些Android特有的數(shù)據(jù)結(jié)構(gòu),比如SpareArray和Pair等,它們都具有更好的性能
? 適當(dāng)使用軟引用和弱引用
? 采用內(nèi)存緩存和磁盤緩存
? 盡量采用靜態(tài)內(nèi)部類,這樣可以避免在的由于內(nèi)部類而導(dǎo)致的內(nèi)存泄露
內(nèi)存泄露分析之MAT工具
MAT的全稱是Eclipse Memory Analyzer,他是一款強(qiáng)大的內(nèi)存泄露分析工具。MAT提供了很多功能,但是最常用的只有Histogram和Dominator Tree,通過Histogram可以直觀看出內(nèi)存中不同類型的buffer的數(shù)量和占用的內(nèi)存大小,而Dominator Tree則把內(nèi)存中的對(duì)象按照從大到小的順序進(jìn)行排序,并且可以分析對(duì)象之間的引用關(guān)系,內(nèi)存泄露分析就是通過Dominator Tree來完成。在Dominator Tree中內(nèi)存泄露的原因一般不會(huì)直接顯示出來,這個(gè)時(shí)候需要按照從大到小的順序去排查一遍。
提高程序的可維護(hù)性
主要是提高代碼的可維護(hù)性和可擴(kuò)展性,而程序的可維護(hù)性本質(zhì)上也包含可擴(kuò)展性。
? 命名要規(guī)范,要能正確地傳達(dá)出變量或者方法的含義,少用縮寫,關(guān)于變量的前綴可以參考Android源碼的命名方式,比如私有方式以m開頭,靜態(tài)成員以s開頭,常量則全部用大寫字母表示,等等。
? 代碼的排版上需要留出合理的空白區(qū)分不同的代碼塊,其中同類變量的聲明要放在一組,兩類變量之間要留出一行空白作為區(qū)分。
? 僅為非常關(guān)鍵的代碼添加注釋,其他地方不寫注釋,這就對(duì)變量和方法的命名風(fēng)格提出了很高的要求。
? 代碼的層次性指代碼要有分層的概念,對(duì)于一段業(yè)務(wù)邏輯,不要試圖在一個(gè)方法或者一個(gè)類中去全部實(shí)現(xiàn),它可以分成幾個(gè)子邏輯,然后每個(gè)子邏輯做自己的事情。單一職責(zé)是和層次性相關(guān)聯(lián),代碼分成以后,每一層僅僅關(guān)注少量的邏輯,這樣就做到了單一職責(zé)。
評(píng)論