androidOOM處理

問題: 安卓系統經常遇到OOM問題,如何優化和應對?

導致OOM 有以下幾種情況:

1 應用中需要加載大對象,例如Bitmap

解決方案:當我們需要顯示大的bitmap對象或者較多的bitmap的時候,就需要進行壓縮來防止OOM問題。我們可以通過設置BitmapFactory.Optiions的inJustDecodeBounds屬性為true,這樣的話不會加載圖片到內存中,但是會將圖片的width和height屬性讀取出來,我們可以利用這個屬性來對bitmap進行壓縮。

另外可以通過對象池來減少gc

對於不用的Bitmap對象,我們要及時回收,否則會造成Memory leak ,所以當我們確定Bitmap對象不用的時候要及時調用Bitmap.recycle()方法來使它盡早被GC

盡量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource來設置一張大圖,因為這些函數在完成decode後,最終都是通過java層的createBitmap來完成的,需要消耗更多內存。

  因此,改用先通過BitmapFactory.decodeStream方法,創建出一個bitmap,再將其設為ImageView的 source,decodeStream最大的秘密在於其直接調用JNI>>nativeDecodeAsset()來完成decode,無需再使用java層的createBitmap,從而節省瞭java層的空間。

 

2 持有無用的對象使其無法被gc,導致Memory Leak . 也就是我們說的內存泄漏。導致內存泄漏我瞭解的有以下幾個方面。

靜態變量導致的Memory leak

不合理使用Context 導致的Memory leak

非靜態內部類導致的Memory leak

Drawable對象的回調隱含的Memory leak

 

3 對於設備本身對應用head內存大小的限制,我們可以自己定義大小Android堆內存

在Android中這個上限值默認是“16m”,而你可以根據實際的硬件配置來調整這個上限值,調整的方法是在系統啟動時加載的某個配置文件中設置一個系統屬性:

dalvik.vm.heapsize=24m

對於一些Android項目,影響性能瓶頸的主要是Android自己內存管理機制問題,目前手機廠商對RAM都比較吝嗇,對於軟件的流暢性來說RAM對性能的影響十分敏感,除瞭 優化Dalvik虛擬機的堆內存分配外,我們還可以強制定義自己軟件的對內存大小,我們使用Dalvik提供的 dalvik.system.VMRuntime類來設置最小堆內存為例:

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;

VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //設置最小heap內存為6MB大小。當然對於內存吃緊來說還可以通過手動幹涉GC去處理

 

4,當我們在切換視圖屏幕的時候(橫豎屏),就會重新建立橫屏或者豎屏的Activity。我們形象的認為之前建立的Activity會被回收,但是事實如何呢?Java機制不會給你同樣的感受,在我們釋放Activity之前,因為run函數沒有結束,這樣MyThread並沒有銷毀,因此引用它的Activity(Mytest)也有沒有被銷毀,因此也帶來的內存泄露問題。

有些人喜歡用Android提供的AsyncTask,但事實上AsyncTask的問題更加嚴重,Thread隻有在run函數不結束時才出現這種內存泄露問題,然而AsyncTask內部的實現機制是運用瞭ThreadPoolExcutor,該類產生的Thread對象的生命周期是不確定的,是應用程序無法控制的,因此如果AsyncTask作為Activity的內部類,就更容易出現內存泄露的問題。

線程問題的改進方式主要有:

l 將線程的內部類,改為靜態內部類。

l 在程序中盡量采用弱引用保存Context。

 

5,被JNI中的指針引用著

Framework中的一些類經常會在Java層創建一個對象,同時也在C++層創建一個對象,然後通過JNI讓這兩個對象相互引用(保存對方的地址),BinderProxy對象就是一個很典型的例子,在這種情況下,Java層的對象同樣不會被釋放。

當泄漏的內存隨著程序的運行越來越多時,最終就會達到heapsize設定的上限值,此時虛擬機就會拋出OutOfMemoryError錯誤,內存溢出瞭。

 

6,Cursor對象未正確關閉

 

(1) 實際在使用的時候代碼的邏輯通常會比上述示例要復雜的多,但總的原則是一定要在使用完畢Cursor以後正確的關閉。

(2) 如果你的Cursor需要在Activity的不同的生命周期方法中打開和關閉,那麼一般可以這樣做:

在onCreate()中打開,在onDestroy()中關閉;

在onStart() 中打開,在onStop() 中關閉;

在onResume()中打開,在onPause() 中關閉;

即要在成對的生命周期方法中打開/關閉。

(3) 如果程序中使用瞭CursorAdapter(例如Music),那麼可以使用它的changeCursor(Cursor cursor)方法同時完成關閉舊Cursor使用新Cursor的操作。

(4) 至於在cursor.close時需不需要try…catch(cursor非空時),其實在close時做的工作就是釋放資源,包括通過Binder跨進程註銷ContentObserver時已經捕獲瞭RemoteException異常,所以其實可以不用try…catch。

(5) 關於deactive和close,deactive不等同於close,看他們的API comments就能知道,如果deactive瞭一個Cursor,說明以後還是會用到它(利用requery方法),這個Cursor會釋放一部分資源,但是並沒有完全釋放;如果確認不再使用這個Cursor瞭,一定要close。

(6)除瞭Cursor有時我們也會對Database對象做操作,例如要修正MediaProvider中的一個attachVolume方法,在每次檢測到attach的是一個external的volume時就重新建立一個數據庫,而不是采用以前的,那麼在remove舊的數據庫對象的時候不要忘記關閉它。

文件描述符泄漏

當然有可能很幸運,每次查詢的結果集都很小,做幾千次查詢都不會內存溢出,但是Android的Linux內核還有另外一個限制,就是文件描述符的上限,這個上限默認是1024。

文件描述符本身是一個整數,用來表示每一個被進程所打開的文件和Socket,第一個打開的文件是0,第二個是1,依此類推。而Linux給每個進程能打開的文件數量設置瞭一個上限,可以使用命令“ulimit -n”查看。另外,操作系統還有一個系統級的限制。

每次創建一個Cursor對象,都會向內核申請創建一塊共享內存,這塊內存以文件形式提供給應用進程,應用進程會獲得這個文件的描述符,並將其映射到自己的進程空間中。如果有大量的Cursor對象沒有正常關閉,可想而知就會有大量的共享內存的文件描述符無法關閉,同時再加上應用進程中的其他文件描述符,就很容易達到1024這個上限,一旦達到,進程就掛掉瞭。

7,正確註冊/註銷監聽器對象

經常要用到一些XxxListener對象,或者是XxxObserver、XxxReceiver對象,然後用registerXxx方法註冊,用unregisterXxx方法註銷。本身用法也很簡單,但是從一些實際開發中的代碼來看,仍然會有一些問題:

(1) registerXxx和unregisterXxx方法的調用通常也和Cursor的打開/關閉類似,在Activity的生命周期中成對的出現即可:

在 onCreate() 中 register,在 onDestroy() 中 unregitster;

在 onStart() 中 register,在 onStop() 中 unregitster;

在 onResume() 中 register,在 onPause() 中 unregitster;

(2) 忘記unregister

以前看到過一段代碼,在Activity中定義瞭一個PhoneStateListener的對象,將其註冊到TelephonyManager中:

TelephonyManager.listen(l,PhoneStateListener.LISTEN_SERVICE_STATE);

但是在Activity退出的時候註銷掉這個監聽,即沒有調用以下方法:

TelephonyManager.listen(l,PhoneStateListener.LISTEN_NONE);

因為PhoneStateListener的成員變量callback,被註冊到瞭TelephonyRegistry中,TelephonyRegistry是後臺的一個服務會一直運行著。所以如果不註銷,則callback對象無法被釋放,PhoneStateListener對象也就無法被釋放,最終導致Activity對象無法被釋放。

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。