Android內存優化方式

作者:閉關寫代碼

鏈接:https://www.zhihu.com/question/19772290/answer/74224654

來源:知乎

著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。

1. 使用更加輕量的數據結構

例如,我們可以考慮使用ArrayMap/SparseArray而不是HashMap等傳統數據結構。通常的HashMap的實現方式更加消耗內存,因為它需要一個額外的實例對象來記錄Mapping操作。另外,SparseArray更加高效,在于他們避免了對key與value的自動裝箱(autoboxing),并且避免了裝箱后的解箱。

2. 避免在Android里面使用Enum

Android官方培訓課程提到過“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具體原理請參考《Android性能優化典范(三)》,所以請避免在Android里面使用到枚舉。

3. 減小Bitmap對象的內存占用

Bitmap是一個極容易消耗內存的大胖子,減小創建出來的Bitmap的內存占用可謂是重中之重,,通常來說有以下2個措施:

inSampleSize:縮放比例,在把圖片載入內存之前,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入。

decode format:解碼格式,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異

4.Bitmap對象的復用

縮小Bitmap的同時,也需要提高BitMap對象的復用率,避免頻繁創建BitMap對象,復用的方法有以下2個措施

LRUCache: “最近最少使用算法”在Android中有極其普遍的應用。ListView與GridView等顯示大量圖片的控件里,就是使用LRU的機制來緩存處理好的Bitmap,把近期最少使用的數據從緩存中移除,保留使用最頻繁的數據,

inBitMap高級特性:利用inBitmap的高級特性提高Android系統在Bitmap分配與釋放執行效率。使用inBitmap屬性可以告知Bitmap解碼器去嘗試使用已經存在的內存區域,新解碼的Bitmap會嘗試去使用之前那張Bitmap在Heap中所占據的pixel data內存區域,而不是去問內存重新申請一塊區域來存放Bitmap。利用這種特性,即使是上千張的圖片,也只會僅僅只需要占用屏幕所能夠顯示的圖片數量的內存大小

4. 使用更小的圖片

在涉及給到資源圖片時,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片。盡量使用更小的圖片不僅可以減少內存的使用,還能避免出現大量的InflationException。假設有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖時會因為內存不足而發生InflationException,這個問題的根本原因其實是發生了OOM。

5.StringBuilder

在有些時候,代碼中會需要使用到大量的字符串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”。

6.避免在onDraw方法里面執行對象的創建

類似onDraw等頻繁調用的方法,一定需要注意避免在這里做創建對象的操作,因為他會迅速增加內存的使用,而且很容易引起頻繁的gc,甚至是內存抖動。

7. 避免對象的內存泄露

類的靜態變量持有大數據對象

靜態變量長期維持到大數據對象的引用,阻止垃圾回收。

非靜態內部類存在靜態實例

非靜態內部類會維持一個到外部類實例的引用,如果非靜態內部類的實例是靜態的,就會間接長期維持著外部類的引用,阻止被回收掉。

資源對象未關閉

資源性對象比如(Cursor,File文件等)往往都用了一些緩沖,我們在不使用的時候,應該及時關閉它們, 以便它們的緩沖及時回收內存。它們的緩沖不僅存在于java虛擬機內,還存在于java虛擬機外。 如果我們僅僅是把它的引用設置為null,而不關閉它們,往往會造成內存泄露。

解決辦法: 比如SQLiteCursor(在析構函數finalize(),如果我們沒有關閉它,它自己會調close()關閉), 如果我們沒有關閉它,系統在回收它時也會關閉它,但是這樣的效率太低了。 因此對于資源性對象在不使用的時候,應該調用它的close()函數,將其關閉掉,然后才置為null. 在我們的程序退出時一定要確保我們的資源性對象已經關閉。 程序中經常會進行查詢數據庫的操作,但是經常會有使用完畢Cursor后沒有關閉的情況。如果我們的查詢結果集比較小, 對內存的消耗不容易被發現,只有在常時間大量操作的情況下才會復現內存問題,這樣就會給以后的測試和問題排查帶來困難和風險,記得try catch后,在finally方法中關閉連接

Handler內存泄漏

Handler作為內部類存在于Activity中,但是Handler生命周期與Activity生命周期往往并不是相同的,比如當Handler對象有Message在排隊,則無法釋放,進而導致本該釋放的Acitivity也沒有辦法進行回收。

解決辦法

聲明handler為static類,這樣內部類就不再持有外部類的引用了,就不會阻塞Activity的釋放

如果內部類實在需要用到外部類的對象,可在其內部聲明一個弱引用引用外部類。

publicclassMainActivityextendsActivity{privateCustomHandlermHandler;@OverrideprotectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);mHandler=newCustomHandler(this);}staticclassCustomHandlerextendsHandler{// 內部聲明一個弱引用,引用外部類privateWeakReferenceactivityWeakReference;publicMyHandler(MyActivityactivity){activityWeakReference=newWeakReference(activity);}// ... ...}}

在Activity onStop或者onDestroy的時候,取消掉該Handler對象的Message和Runnable

@OverridepublicvoidonDestroy(){//? If null, all callbacks and messages will be removed.mHandler.removeCallbacksAndMessages(null);}

一些不良代碼習慣

有些代碼并不造成內存泄露,但是他們的資源沒有得到重用,頻繁的申請內存和銷毀內存,消耗CPU資源的同時,也引起內存抖動

解決方案

如果需要頻繁的申請內存對象和和釋放對象,可以考慮使用對象池來增加對象的復用。 例如ListView便是采用這種思想,通過復用converview來避免頻繁的GC

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容