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舊的資料庫物件的時候不要忘記關閉它。<!-- 第6點關於Database是否考慮去掉 -->
檔案描述符泄漏
當然有可能很幸運,每次查詢的結果集都很小,做幾千次查詢都不會記憶體溢出,但是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對象無法被釋放。