編輯:關於Android編程
本篇總結了Android開發常見過程中的內存洩漏問題。
??集合類如果僅僅有添加元素的方法,而沒有相應的刪除機制,導致內存被占用。如果這個集合類是全局性的變量 (比如類中的靜態屬性,全局性的 map 等即有靜態引用或 final 一直指向它),那麼沒有相應的刪除機制,很可能導致集合所占用的內存只增不減。
由於單例的靜態特性使得其生命周期跟應用的生命周期一樣長,所以如果使用不恰當的話,很容易造成內存洩漏。比如下面一個典型的例子,
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context; } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
這是一個普通的單例模式,當創建這個單例的時候,由於需要傳入一個Context,所以這個Context的生命周期的長短至關重要:
1、如果此時傳入的是 Application 的 Context,因為 Application 的生命周期就是整個應用的生命周期,所以這將沒有任何問題。
2、如果此時傳入的是 Activity 的 Context,當這個 Context 所對應的 Activity 退出時,由於該 Context 的引用被單例對象所持有,其生命周期等於整個應用程序的生命周期,所以當前 Activity 退出時它的內存並不會被回收,這就造成洩漏了。
正確的方式應該改為下面這種方式:
public class AppManager { private static AppManager instance; private Context context; private AppManager(Context context) { this.context = context.getApplicationContext();// 使用Application 的context } public static AppManager getInstance(Context context) { if (instance == null) { instance = new AppManager(context); } return instance; } }
或者這樣寫,連 Context 都不用傳進來了:
在你的 Application 中添加一個靜態方法,getContext() 返回 Application 的 context,
... context = getApplicationContext(); ... /** * 獲取全局的context * @return 返回全局context對象 */ public static Context getContext(){ return context; } public class AppManager { private static AppManager instance; private Context context; private AppManager() { this.context = MyApplication.getContext();// 使用Application 的context } public static AppManager getInstance() { if (instance == null) { instance = new AppManager(); } return instance; } }
有的時候我們可能會在啟動頻繁的Activity中,為了避免重復創建相同的數據資源,可能會出現這種寫法:
public class MainActivity extends AppCompatActivity { private static TestResource mResource = null; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); if(mManager == null){ mManager = new TestResource(); } //... } class TestResource { //... } }
這樣就在Activity內部創建了一個非靜態內部類的單例,每次啟動Activity時都會使用該單例的數據,這樣雖然避免了資源的重復創建,不過這種寫法卻會造成內存洩漏,因為非靜態內部類默認會持有外部類的引用,而該非靜態內部類又創建了一個靜態的實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。正確的做法為:
將該內部類設為靜態內部類或將該內部類抽取出來封裝成一個單例,如果需要使用Context,請按照上面推薦的使用Application 的 Context。當然,Application 的 context 不是萬能的,所以也不能隨便亂用,對於有些地方則必須使用 Activity 的 Context,對於Application,Service,Activity三者的Context的應用場景如下:
其中: NO1表示 Application 和 Service 可以啟動一個 Activity,不過需要創建一個新的 task 任務隊列。而對於 Dialog 而言,只有在 Activity 中才能創建<喎?/kf/ware/vc/" target="_blank" class="keylink">vcD4NCjxoMiBpZD0="匿名內部類">匿名內部類
android開發經常會繼承實現Activity/Fragment/View,此時如果你使用了匿名類,並被異步線程持有了,那要小心了,如果沒有任何措施這樣一定會導致洩露
public class MainActivity extends Activity { ... Runnable ref1 = new MyRunable(); Runnable ref2 = new Runnable() { @Override public void run() { } }; ... }
ref1和ref2的區別是,ref2使用了匿名內部類。我們來看看運行時這兩個引用的內存:
可以看到,ref1沒什麼特別的。
但ref2這個匿名類的實現對象裡面多了一個引用:
this$0這個引用指向MainActivity.this,也就是說當前的MainActivity實例會被ref2持有,如果將這個引用再傳入一個異步線程,此線程和此Acitivity生命周期不一致的時候,就造成了Activity的洩露。
??Handler 的使用造成的內存洩漏問題應該說是最為常見了,很多時候我們為了避免 ANR 而不在主線程進行耗時操作,在處理網絡任務或者封裝一些請求回調等api都借助Handler來處理,但 Handler 不是萬能的,對於 Handler 的使用代碼編寫一不規范即有可能造成內存洩漏。另外,我們知道 Handler、Message 和 MessageQueue 都是相互關聯在一起的,萬一 Handler 發送的 Message 尚未被處理,則該 Message 及發送它的 Handler 對象將被線程 MessageQueue 一直持有。
由於 Handler 屬於 TLS(Thread Local Storage) 變量, 生命周期和 Activity 是不一致的。因此這種實現方式一般很難保證跟 View 或者 Activity 的生命周期保持一致,故很容易導致無法正確釋放。
舉個例子:
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mLeakyHandler.postDelayed(new Runnable() { @Override public void run() { /* ... */ } }, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
在該 SampleActivity 中聲明了一個延遲10分鐘執行的消息 Message,mLeakyHandler 將其 push 進了消息隊列 MessageQueue 裡。當該 Activity 被 finish() 掉時,延遲執行任務的 Message 還會繼續存在於主線程中,它持有該 Activity 的 Handler 引用,所以此時 finish() 掉的 Activity 就不會被回收了從而造成內存洩漏(因 Handler 為非靜態內部類,它會持有外部類的引用,在這裡就是指 SampleActivity)。
修復方法:在 Activity 中避免使用非靜態內部類,比如上面我們將 Handler 聲明為靜態的,則其存活期跟 Activity 的生命周期就無關了。同時通過弱引用的方式引入 Activity,避免直接將 Activity 作為 context 傳進去,見下面代碼:
public class SampleActivity extends Activity { /** * Instances of static inner classes do not hold an implicit * reference to their outer class. */ private static class MyHandler extends Handler { private final WeakReferencemActivity; public MyHandler(SampleActivity activity) { mActivity = new WeakReference (activity); } @Override public void handleMessage(Message msg) { SampleActivity activity = mActivity.get(); if (activity != null) { // ... } } } private final MyHandler mHandler = new MyHandler(this); /** * Instances of anonymous classes do not hold an implicit * reference to their outer class when they are "static". */ private static final Runnable sRunnable = new Runnable() { @Override public void run() { /* ... */ } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Post a message and delay its execution for 10 minutes. mHandler.postDelayed(sRunnable, 1000 * 60 * 10); // Go back to the previous Activity. finish(); } }
綜述,即推薦使用靜態內部類 + WeakReference 這種方式。每次使用前注意判空。
前面提到了 WeakReference,所以這裡就簡單的說一下 Java 對象的幾種引用類型。
Java對引用的分類有 Strong reference, SoftReference, WeakReference, PhatomReference 四種。
在Android應用的開發中,為了防止內存溢出,在處理一些占用內存大而且聲明周期較長的對象時候,可以盡量應用軟引用和弱引用技術。
軟/弱引用可以和一個引用隊列(ReferenceQueue)聯合使用,如果軟引用所引用的對象被垃圾回收器回收,Java虛擬機就會把這個軟引用加入到與之關聯的引用隊列中。利用這個隊列可以得知被回收的軟/弱引用的對象列表,從而為緩沖器清除已失效的軟/弱引用。
假設我們的應用會用到大量的默認圖片,比如應用中有默認的頭像,默認游戲圖標等等,這些圖片很多地方會用到。如果每次都去讀取圖片,由於讀取文件需要硬件操作,速度較慢,會導致性能較低。所以我們考慮將圖片緩存起來,需要的時候直接從內存中讀取。但是,由於圖片占用內存空間比較大,緩存很多圖片需要很多的內存,就可能比較容易發生OutOfMemory異常。這時,我們可以考慮使用軟/弱引用技術來避免這個問題發生。以下就是高速緩沖器的雛形:
首先定義一個HashMap,保存軟引用對象。
private Map> imageCache = new HashMap > ();
再來定義一個方法,保存Bitmap的軟引用到HashMap。
使用軟引用以後,在OutOfMemory異常發生之前,這些緩存的圖片資源的內存空間可以被釋放掉的,從而避免內存達到上限,避免Crash發生。
如果只是想避免OutOfMemory異常的發生,則可以使用軟引用。如果對於應用的性能更在意,想盡快回收一些占用內存比較大的對象,則可以使用弱引用。
另外可以根據對象是否經常使用來判斷選擇軟引用還是弱引用。如果該對象可能會經常使用的,就盡量用軟引用。如果該對象不被使用的可能性更大些,就可以用弱引用。
ok,繼續回到主題。前面所說的,創建一個靜態Handler內部類,然後對 Handler 持有的對象使用弱引用,這樣在回收時也可以回收 Handler 持有的對象,但是這樣做雖然避免了 Activity 洩漏,不過 Looper 線程的消息隊列中還是可能會有待處理的消息,所以我們在 Activity 的 Destroy 時或者 Stop 時應該移除消息隊列 MessageQueue 中的消息。
下面幾個方法都可以移除 Message:
public final void removeCallbacks(Runnable r); public final void removeCallbacks(Runnable r, Object token); public final void removeCallbacksAndMessages(Object token); public final void removeMessages(int what); public final void removeMessages(int what, Object object);
如果成員變量被聲明為 static,那我們都知道其生命周期將與整個app進程生命周期一樣。
這會導致一系列問題,如果你的app進程設計上是長駐內存的,那即使app切到後台,這部分內存也不會被釋放。按照現在手機app內存管理機制,占內存較大的後台進程將優先回收,如果此app做過進程保活,那會造成app在後台頻繁重啟。當手機安裝了你參與開發的app以後一夜時間手機被消耗空了電量、流量,你的app不得不被用戶卸載或者靜默。
這裡修復的方法是:
不要在類初始時初始化靜態成員。可以考慮lazy初始化。 架構設計上要思考是否真的有必要這樣做,盡量避免。如果架構需要這麼設計,那麼此對象的生命周期你有責任管理起來。
1、finalize 方法被執行的時間不確定,不能依賴與它來釋放緊缺的資源。時間不確定的原因是: 虛擬機調用GC的時間不確定 Finalize daemon線程被調度到的時間不確定
2、finalize 方法只會被執行一次,即使對象被復活,如果已經執行過了 finalize 方法,再次被 GC 時也不會再執行了,原因是:
含有 finalize 方法的 object 是在 new 的時候由虛擬機生成了一個 finalize reference 在來引用到該Object的,而在 finalize 方法執行的時候,該 object 所對應的 finalize Reference 會被釋放掉,即使在這個時候把該 object 復活(即用強引用引用住該 object ),再第二次被 GC 的時候由於沒有了 finalize reference 與之對應,所以 finalize 方法不會再執行。
3、含有Finalize方法的object需要至少經過兩輪GC才有可能被釋放。
對於使用了BraodcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將不會被回收,造成內存洩漏。
有些代碼並不造成內存洩露,但是它們,或是對沒使用的內存沒進行有效及時的釋放,或是沒有有效的利用已有的對象而是頻繁的申請新內存。
比如: Bitmap 沒調用 recycle()方法,對於 Bitmap 對象在不使用時,我們應該先調用 recycle() 釋放內存,然後才它設置為 null. 因為加載 Bitmap 對象的內存空間,一部分是 java 的,一部分 C 的(因為 Bitmap 分配的底層是通過 JNI 調用的 )。 而這個 recyle() 就是針對 C 部分的內存釋放。 構造 Adapter 時,沒有使用緩存的 convertView ,每次都在創建新的 converView。這裡推薦使用 ViewHolder。
??對 Activity 等組件的引用應該控制在 Activity 的生命周期之內; 如果不能就考慮使用 getApplicationContext 或者 getApplication,以避免 Activity 被外部長生命周期的對象引用而洩露。
盡量不要在靜態變量或者靜態內部類中使用非靜態外部成員變量(包括context ),即使要使用,也要考慮適時把外部成員變量置空;也可以在內部類中使用弱引用來引用外部類的變量。
對於生命周期比Activity長的內部類對象,並且內部類中使用了外部類的成員變量,可以這樣做避免內存洩漏:
1.將內部類改為靜態內部類
2. 靜態內部類中使用弱引用來引用外部類的成員變量
Handler 的持有的引用對象最好使用弱引用,資源釋放時也可以清空 Handler 裡面的消息。比如在 Activity onStop 或者 onDestroy 的時候,取消掉該 Handler 對象的 Message和 Runnable.
在 Java 的實現過程中,也要考慮其對象釋放,最好的方法是在不使用某對象時,顯式地將此對象賦值為 null,比如使用完Bitmap 後先調用 recycle(),再賦為null,清空對圖片等資源有直接引用或者間接引用的數組(使用 array.clear() ; array = null)等,最好遵循誰創建誰釋放的原則。
正確關閉資源,對於使用了BroardcastReceiver,ContentObserver,File,游標 Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷。
保持對對象生命周期的敏感,特別注意單例、靜態對象、全局性集合等的生命周期。
版本:1.0 日期:2014.6.11 2014.6.12版權:© 2014 kince 轉載注明出處 ImageView是開發中經常使用到的一個控件,也可以
位置定位(Location)服務(Service)類的基本操作本文地址: http://blog.csdn.net/caroline_wendy定位服務(Location
概述:項目地址:https://github.com/nostra13/Android-Universal-Image-Loader UIL(Universal-Imag
Android系統已經被各大廠商改的面目全非,各種系統組件在不同手機上顯示的效果完全不同,現在要想在開發中想要保持統一的界面風格,必須全部修改系統組件的樣式,現在我們需要