Android內存優化建議

        這篇博客主要是總結一些內存優化技巧,大體包括編碼優化、Bitmap優化優化ListView減少內存開銷、佈局優化、其他優化:
        編碼優化:
        (1):使用更加輕量級的數據結構
        使用ArrayMap和SparseArray代替我們常見的HashMap,因爲對於HashMap來說,它本身是由數組加鏈表實現的,通常爲了他的mapping操作,我們需要開闢一段數組空間,但是很多情況下這段數組空間是不會得到合理利用的,針對這一點,我們可以使用ArrayMap來代替他,ArrayMap的實現原理是通過兩個數組實現的,一個數組用來存儲key值對應的hash值,這個數組中的hash值是順序存儲的,另一個數組中存儲的是對應於第一個數組hash順序的鍵值對,對於put操作,首先先計算出當前key對應的hash值,並將其放入到第一個數組中,注意必須是順序的,也就是說可能會對第一個數組進行排序,那麼在排序的過程中,第一個數組在發生相應調整的同時第二個數組也要跟着進行調整,在找到key值在第一個數組中對應的hash所處的位置之後,會直接查看第二個數組中對應於這個位置index處是否存在鍵值對,如果存在的話,則會向上或者向下搜索一個空閒的位置,將當前鍵值對插入,如果不存在的話,則直接插入就可以了;對於get操作的話,首先通過key值計算出來一個hash值,然後根據該hash值通過二分查找的方法找到他在第一個數組中的位置index,接着查看第二個數組中的index位置處的鍵值對的key值是否equals當前鍵,如果equals的話,則返回該鍵值對應的value就可以了,如果不equals的話,則通過向下或者向上搜索的方式去查找,知道找到或者到達數組邊界爲止;除此之外呢,HashMap需要爲每一個鍵值對都提供一個對象入口,而SparseArray避免了基本數據類型轉換爲對象數據類型的封箱和解箱操作;
        (2):對常量使用static-final修飾符
        對於基本數據類型以及String類型的常量,儘量聲明爲是static-final類型的,這樣的話,這些常量會在dex文件的初始化器中進行初始化,而不再是通過類的<clinit>方法初始化了;
        (3):減少枚舉類型的使用
        因爲枚舉類型在使用的過程中會產生大量的中間元素;
        (4):如果存在大量的字符串拼接操作的話,使用StringBuffer或者StringBuilder代替+運算符,因爲使用+運算符的話會產生大量的中間元素;
        (5):採用非靜態內部類或者匿名內部類時
        因爲對於非靜態內部類或者匿名內部類的話,默認情況下是存在一個外部類的引用的,如果我們的內部類的存活期限不會超過外部類的話,那麼是不會出現什麼問題的,但是如果內部類的存活期限要長於外部類的話,就會出現即使外部類已經不再使用了,但是它所佔用的內存空間還是不會得到釋放的情況出現,這種現象很頻繁的出現在我們使用Handler的時候,如果Activity已經退出,但是Handler還有延遲處理的消息沒有處理完,那麼這時候就會導致Activity不能回收導致內存泄漏,這時候的解決方案是將Handler聲明爲static類型,在外部類中實例化一個外部類的WeakReference(弱引用),並且在Handler初始化的時候傳入這個對象給Handler,這樣在Handler內部使用WeakReference對象來引用外部類成員;
        Bitmap優化:
        (1):儘量使用較小的圖片
        使用較小的圖片,不僅可以減少OOM異常,同時也能減少InflateException異常,因爲我們可能在剛剛加載圖片的時候就可能因爲內存不足而加載失敗,只時候報的是InflateException異常;
        (2):減少Bitmap多佔用的內存空間
        減少Bitmap佔用內存空間的方法有:進行適當的圖片壓縮以及選用合適的圖片解碼器;具體來講的話,在我們將圖片加載到內存之前,可以通過設置加載圖片的inJustDecodeBounds屬性爲false,這樣的話只會將圖片的頭部信息加載到內存,我們可以得到圖片的寬度和高度,接着根據顯示圖片的控件的寬度和高度計算出一定的壓縮比,將值設置到圖片的inSampleSize屬性,接着設置加載圖片的inJustDecodeBounds屬性爲true,這時候再次加載圖片的時候,加載到內存中的就是壓縮過後的啦;此外,採用不同的圖片解碼方式,佔用的內存空間也是不同的,對於ARGB_8888編碼的圖片,其每個像素佔用4個字節,對於其他的圖片編碼方式,每個像素佔用的字節數也是有所不同的;
        (3):使用緩存機制減少Bitmap佔用內存的大小
        我們可以使用LRUCache內存緩存和DiskLruCache來減少Bitmap佔用的內存空間大小;
        (4):使用inBitmap高級屬性
        Bitmap的inBitmap高級屬性會大大提升Android系統在分配和釋放Bitmap佔用空間上的效率,具體來講的話,對於每一個想要加載的Bitmap,inBitmap屬性會告知圖片解碼器優先使用已經在內存中的Bitmap區域,而不是重新開闢一段內存空間,但是要想真正使用好inBitmap高級屬性的話,需要具備兩個條件,我們新申請的Bitmap大小必須小於等於舊Bitmap的大小,此外新舊Bitmap的圖片解碼器類型也應該一致,要使用ARGB_8888就都是ARGB_8888,不過,爲了優化,我們可以設置一個可重用Bitmap的對象池,這樣後續的Bitmap到來之後都可以從池子裏面找到一個適合自己的模板去複用啦!
        優化ListView減少內存開銷:
        (1):重用ConvertView,在ListView中Android的RecycleBin機制是通過生產者消費者模式實現的,任何移出屏幕的Item在下一時刻都會被移入屏幕的Item重新利用起來,我們應該充分利用這種機制,因爲構造View是需要通過LayoutInflater的inflate方法通過pull解析的方式進行的,這個過程是涉及到IO操作的;
        (2):儘量減少findViewById的次數,因爲findViewById方法也是涉及到IO操作的;
        (3):如果存在加載大量圖片的話,在快速滑動的時候應該停止圖片的加載;
        (4):儘量保證Adapter的hasStables方法返回true,這樣在調用Adapter的notifyDataSetChanged方法的時候,如果當前ListView中的Item元素沒有發生變化的話,就不需要重繪ListView了;
        (5):儘量減少每個Item的層級,這樣會減少視圖重繪帶來的內存開銷;
        (6):使用RecycleView代替ListView,對於ListView來說,只要有一個Item發生變化,都會回調Adapter的notifyDataSetChanged方法,該方法會導致整個ListView重繪,但是RecycleView是支持局部刷新的,哪個Item發生變化,只需要重繪該Item就可以了,沒必要重繪全部View,還有就是RecycleView提供了Item的插入、刪除的動畫效果,在性能和個性化定製方面更加出色;
        (7):開啓硬件加速;
        (8):分頁加載;
        佈局優化:思想就是儘量減少佈局文件的層級;

        (1):如果佈局既可以通過RelativeLayout又可以通過LinearLayout實現的話,選擇通過LinearLayout實現,因爲RelativeLayout的功能相對於LinearLayout來說更加複雜,佈局過程需要更多的CPU開銷;如果某些佈局能夠通過RelativeLayout來實現,也可以通過嵌套LinearLayout來實現,這時候應該選擇RelativeLayout來實現,因爲嵌套相當於增加了佈局的層級,帶來的性能損失比佈局RelativeLayout更大;

        (2):使用<include>和<merge>標籤來進行佈局重用,減少佈局的層級;關於<include>和<merge>的使用,大家可以查看郭神的這篇博客,我在此僅僅總結下:在<include>標籤中可以指定一個layout屬性,填寫需要引入的佈局名就可以啦,此外呢,我們可以在<include>中覆寫所有layout屬性,並且<include>中設置之後的屬性值將會替換到原先複用佈局文件中的對應屬性,對於非layout屬性則無法在<include>標籤中進行覆寫,如果我們想要在<include>中覆寫layout屬性,必須將layout_width和layout_height屬性同時覆寫,否則不會有任何效果;<merge>標籤是配合<include>起作用的,因爲在我們將某一佈局文件<include>到某一佈局中的時候,會出現多餘的佈局,即此佈局是重複的,本來外層佈局已經可以達到效果了,你又加了一層佈局,佈局層次增加,增加了繪製的開銷,如果將要加進來的佈局使用<merge>標籤的話,那麼他就會真正意義上把當前佈局添加到原先佈局中去,而不會產生多餘的佈局,出現佈局嵌套現象;

        (3):使用<ViewStub>標籤,這個標籤能夠做到動態加載我們的佈局,不同於我們通過GONE的方式來設置某個View是否可見,ViewStub是View的一種,但是它沒有大小,沒有繪製功能,也不參與佈局,資源消耗非常低,這點是不同於我們通過GONE方式設置View不可見的,因爲通過GONE方式設置View不可見了說到底還是會繪製View的,只不過你看不到而已;需要注意的是,雖然ViewStub沒有寬高,但是每個佈局都是必須要指定layout_width和layout_height屬性的,我們通常想要顯示某一個ViewStub的時候可以通過setVisibility(View.VISIBLE)和LayoutInflater的inflate方法將其顯示出來,除此之外,ViewStub是不能和<merge>配合使用的,因此使用ViewStub可能會帶來佈局嵌套的問題;

        其他優化:

        (1):針對單例模式可能帶來的內存溢出現象,在創建單例的時候,一定要注意傳入的Context上下文對象的類型,最好是Application對象;

        (2):儘量不要在onDraw方法裏面創建新的局部對象,因爲onDraw方法被調用的比較頻繁,這樣會產生大量的臨時對象,會進行頻繁的GC操作,嚴重情況會出現內存抖動現象;

        (3):屬性動畫帶來的內存泄漏;屬性動畫中存在一種無限制循環的動畫,如果在Activity中播放此類動畫且沒有在onDestroy中去停止動畫,那麼動畫會一直播放下去,儘管已經無法在界面上看到動畫效果,並且這時候Activity的View會被動畫持有,而View又持有Activity,最終導致Activity無法釋放;

        (4):有節制的使用Service,在系統中如果用到Service的話來執行後臺任務的話,一定要記得在需要的時候開啓Service,當任務執行結束之後去停止Service,儘量避免停止Service帶來的內存些泄漏問題;如果我們不自己手動結束掉Serviec的話,對於系統來講,它是更傾向於將Service所依賴的進程保留下來的,這回導致Service所在的進程非常耗內存,系統可以並行執行的進程數會減少,嚴重的話會導致系統奔潰;Android官方建議我們使用IntentService來代替普通的Service;

        (5):當界面不可見的時候,釋放掉所有與當前界面相關的資源,可以讓系統緩存後臺進程的能力增強,加強用戶體驗度,那麼怎麼知道界面可不可見呢?只需要重寫Activity的onTrimMemory方法就可以了,然後在該方法裏面監聽TRIM_MEMORY_UI_HIDDEN級別就可以了,在這種級別下釋放掉與當前界面相關的資源就可以了;onTrimMemory和onStop方法還是有很大區別的,像我們通常的onStop方法的話,他會在我們的Activity跳轉的時候被回調,釋放的是一些與Activity有關的資源,而onTrimMemory方法中的TRIM_MEMORY_UI_HIDDEN級別只有我們應用程序的所有UI組件全部不可見的時候會被調用,相當於我們的應用程序處於後臺運行的狀態下,在Activity跳轉的時候是不會執行的,保證了我們在跳轉Activity的時候不需要重新加載界面元素,提升了應用的響應速度;



發佈了124 篇原創文章 · 獲贊 301 · 訪問量 68萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章