Android記憶體洩漏解決方案(OOM)
為什麼會有記憶體洩漏?
一個不會被使用的物件,因為另一個正在使用的物件持有該物件的引用,導致它不能正常被回收,而停留在堆記憶體中,記憶體洩漏就產生了
Android系統為每個應用分配的記憶體是有限的,記憶體洩漏會使我們的應用記憶體隨著時間不斷的增加,造成應用OOM(Out Of Memory)錯誤,使應用崩潰.
如何解決記憶體洩漏?
當我們在解決記憶體洩漏的時候常常使用 LeakCanary工具,它是一個自動檢測記憶體洩漏的開源工具,使用它我們就可以明確的知道那個地方發生了洩漏
持有Context造成的記憶體洩漏
在Android中有兩種context物件:Activity和Application.當我們給一個類傳遞context的時候經常使用第一種,而這樣就導致了改類持有對Activity的全部引用,當Activity關閉的時候因為被其他類持有,而導致無法正常被回收,而導致記憶體洩漏
解決方案:
在給其他給傳遞context的時候使用Application物件,這個物件的生命週期和共存亡,而不依賴activity的宣告週期.
而對context的引用不要超過他本身的生命週期,謹慎對context使用static關鍵字.
Handler造成的記憶體洩漏
public class SampleActivity extends Activity { private final Handler mLeakyHandler = new Handler() { @Override public void handleMessage(Message msg) { // ... } } }
這樣來使用Handler會造成嚴重的記憶體洩漏.
假設Hanlder中有延遲的任務或是等在執行的任務佇列過長,由於訊息佇列持有對handler的引用,而handler又持有activity的隱式引用,這個引用會保持到訊息得到處理,而導致activity無法被垃圾回收器進行回收,而導致記憶體洩漏
解決方案:
- 可以把Handler放到單獨的類中,或者使用靜態的內部類(靜態內部類不會引用activity)避免洩漏
- 如果想要在handler內部去呼叫Activity中的資源,可以在Handler中使用弱引用的方式指向所在的Activity,使用static+WeakReference的方式斷開handler與activity的關係
最終程式碼:
public static class MyHandler extends Handler {
//宣告一個弱引用物件
WeakReference<MainActivity> mReference;
MyHandler(MainActivity activity) {
//在構造器中傳入Activity,建立弱引用物件
mReference = new WeakReference<MainActivity>(activity);
}
public void handleMessage(Message msg) {
//在使用activity之前先判空處理
if (mReference != null && mReference.get() != null) {
mReference.get().text.setText("hello word");
}
}
}
使用單利模式造成的記憶體洩漏
在我們使用單利模式的時候如果使用不當也會造成記憶體洩漏.因為單利模式的靜態特徵使得單利模式的生命週期和應用一樣的長,這說明了當一個物件不需要使用了,而單利物件還存在該物件的引用,那麼這個物件就不能正常的被回收,就造成了記憶體洩漏
解決方案:
XXUtils.getInstance(this);
這句程式碼預設傳入的是Activity的Context,而Activity是間接繼承自Context的,當Activity退出之後,單利物件還持有他的引用,所以在為了避免傳Activity的Context,在單利中通過傳入的context獲取到全域性的上下文物件,而不使用Activity的Context就解決了這個問題.
public class XXUtils {
private Context mContext;
private XXUtils(Context context) {
mContext = context.getApplicationContext();
}
private static XXUtils instance;
public static XXUtils getInstance(Context context) {
if (instance == null) {
synchronized (XXUtils.class) {
if (instance == null) {
instance = new XXUtils(context);
}
}
}
return instance;
}
}
非靜態內部類建立靜態例項造成的記憶體洩漏
在專案中我們為了避免多次的初始化資源,常常會使用靜態物件去儲存這些物件,這種情況也很容易引發記憶體洩漏.
why?
- 非靜態的內部類預設會持有外部類的引用
- 而我們又使用非靜態內部類建立了一個靜態的例項
- 該靜態例項的宣告週期和應用一樣長,這就導致了該靜態例項一直會持有該Activity的引用,導致Activity不能正常回收
解決方案:
- 將內部類修改成靜態的,這樣它對外部類就沒有引用
- 將該物件抽取出來封裝成一個單利.
最終程式碼:
private static TestResource mTestResource;
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.btn).setOnClickListener(this);
}
private void initData() {
if (mTestResource == null) {
mTestResource = new TestResource();
}
}
public void onClick(View v) {
initData();
}
//非靜態內部類預設會持有外部類的引用
//修改成就太之後正常被回收,是因為靜態的內部類不會對Activity有引用
private static class TestResource {
}
執行緒造成的記憶體洩漏
當我們在使用執行緒的時候,一般都使用匿名內部類,而匿名內部類會對外部類持有預設的引用,當Acticity關閉之後如果現成中的任務還沒有執行完畢,就會導致Activity不能正常回收,造成記憶體洩漏
解決方案
建立一個靜態的類,實現Runnable方法,在使用的時候例項化他.
最終程式碼:
private void loadData() {
new Thread(new MyThread()).start();
}
private static class MyThread implements Runnable {
public void run() {
SystemClock.sleep(20000);
}
}
資源未關閉造成的記憶體洩漏
對於使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等資源的程式碼,應該在Activity銷燬時及時關閉或者登出,否則這些資源將不會被回收,造成記憶體洩漏。
監聽器沒有登出造成的記憶體洩漏
在Android程式裡面存在很多需要register與unregister的監聽器,我們需要確保及時unregister監聽器。
集合中的記憶體洩漏
我們通常把一些物件的引用加入到了集合容器(比如ArrayList)中,當我們不需要該物件時,
並沒有把它的引用從集合中清理掉,這樣這個集合就會越來越大。如果這個集合是static的話,那情況就更嚴重了。
所以要在退出程式之前,將集合裡的東西clear,然後置為null,再退出程式。