1. 程式人生 > >Android記憶體洩漏解決方案(OOM)

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無法被垃圾回收器進行回收,而導致記憶體洩漏

解決方案:

  1. 可以把Handler放到單獨的類中,或者使用靜態的內部類(靜態內部類不會引用activity)避免洩漏
  2. 如果想要在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?

  1. 非靜態的內部類預設會持有外部類的引用
  2. 而我們又使用非靜態內部類建立了一個靜態的例項
  3. 該靜態例項的宣告週期和應用一樣長,這就導致了該靜態例項一直會持有該Activity的引用,導致Activity不能正常回收

解決方案:

  1. 將內部類修改成靜態的,這樣它對外部類就沒有引用
  2. 將該物件抽取出來封裝成一個單利.

最終程式碼:

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,再退出程式。