Android 內存泄漏案例分析總結(Handler)

在Android開發開發中,操作不當很容易引起內存泄漏,這裏主要記錄下平時遇到問題,包括:靜態變量(也包含集合)、非靜態的內部類、Handler、監聽器,尤其是 Handler 在開發中要格外的注意。
同步發佈在簡書Android 內存泄漏案例分析總結(Handler)

1、靜態變量

public class LeakActivityDemo extends Activity{
      private static TextView mTextView;

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTextView = new TextView(this);
        mTextView.setText("demo");
      }
    }

我這裏這是一個例子,一般開發不會這麼寫,上面的代碼在 Activity 結束的時候,mTextView 一直持有 this 引用,
導致整個 Activity 無法回收

解決:方法在 Activity 生命週期 onDestroy 時將 mTextView 置空,或者儘量少使用到靜態變量。
注意:在寫代碼時,要考慮到當前的變量是否持有當前 Activity 的引用,避免出現內存泄漏.

2、非靜態內部類

關於 Handler

public class LeakActivity extends Activity{

      @Override
      protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mHandler.postDelayed(new Runnable() {

          @Override
          public void run() {

          }
        }, 1);
      }

      //提示:This Handler class should be static or leaks might occur (com.mvp.view.LeakActivity.1)
      private Handler mHandler = new Handler() {
          @Override public void handleMessage(Message msg) {
              super.handleMessage(msg);
          }
      };
    }

在 Android 開發中,我們一般使用 Handler 處理異步操作,通常我們的代碼會像上面一樣實現,但是上面的代碼可能存在泄漏,其實編譯器已經提示了警告,建議使用 static 靜態標示。

原因:

1、首先在 java 中,非靜態內部類會或者內部類會持有外部對象的引用,而靜態內部類不會持有外部類的引用;

2、和 Handler 機制有關,我們知道 Handler 和 Looper 是一起工作的,在一個 Activity 起來的時候,會幫我們創建一個
在主線程中使用的 Looper,用來處理所有的消息,當 Hanlder 初始化好以後,就會有一個消息發送到了 Looper 的消息隊列中,
而這個消息則包含了當前 Handler 的引用,這就是內存泄露的原因;

解決:

1、使用靜態內部類,如果在內部類中使用了 Activiy 則要使用 WeakReference(弱引用),並且需要注意判空。

2、還有Hanlder要在生命週期 ondestroy 時,取消該 Handler 對象的 Messag 和 Runnable。

例如:
removeCallbacks(Runnable r)、removeMessages(int what)、mHandler.removeCallbacksAndMessages(null);

private final MyHandler mHandler = new MyHandler(this);

  public static class MyHandler extends Handler {
      private final WeakReference<LeakActivity> mWeakActivity;

      public MyHandler(LeakActivity context) {
        mWeakActivity = new WeakReference<LeakActivity>(context);
      }

      @Override public void handleMessage(Message msg) {
          super.handleMessage(msg);
          if (mWeakActivity.get() != null) {
            mWeakActivity.get().todo();
      }
      }
  }

  public void todo(){
    //todo
  }

關於 Runnable

常常會這麼寫,會存在很大的內存泄露問題,內部類會持有外部對象的引用,如果我們 run 方法操作了 UI 等,或者 使用了 postDelayed 方法,很容易造成內存泄露問題。

mHandler.post(new Runnable() {

    @Override
    public void run() {
      ..
    }
});

解決方法:使用靜態的Runnable 和 WeakReference 來解決引用問題,使用 WeakReference 要注意判斷,因爲可能隨時會被回收。

private static final class ItemRunnble implements Runnable {

  private final WeakReference<Item> mWeakReference;

  public ProgressBarRunnble(Item f) {
    mWeakReference = new WeakReference<Item>(f);
  }

  @Override
  public void run() {
    Item item = mWeakReference.get();
    if (item != null) {
      ...
    }
  }
}

mHandler.post(new ItemRunnble(mItem))

3、非靜態內部類生成的靜態變量

private static MyClass myClass;
  private class MyClass {

  }

..

myClass = new MyClass();  

這種是兩者的綜合體,只要是非靜態內部類就會持有外部類的引用,如果外部類正好是 Activity ,那麼會導致 Activity 無法回收;
處理方式和第一種很像,記得釋放

總結

靜態變量最容易出現內存泄露,這些靜態變量的生命週期和應用程序一致,所有的對象 Object 也不能被釋放,
而集合類如果不 remove 添加的 Object,則會永遠持有這些 Ojbect,也會導致無法釋放。

監聽器是一種使用方式,Android 中比較常見的是 Listener,Observer 等,
一般被監聽者的生命週期要比監聽者的生命週期長,當監聽者本身不被使用,但又沒有移除被監聽者對其的引用時就會造成內存無法釋放。

數據庫在不使用時也沒有關閉,那麼這部分內存也就無法回收。

以下列舉注意情況:


  • 遵守生命週期,創建時創建,銷燬時記得回收
  • Bitmip 和 Drawable 記得手動回收
  • 減少靜態變量的使用
  • 自定義靜態 Handler,和 Handler 回收
  • 使用 Application Context,少使用 Activity Context
  • 監聽器不使用的時候記得釋放
  • 如果循環中使用較多臨時變量,當不使用時及時釋放

參考文章

發佈了18 篇原創文章 · 獲贊 32 · 訪問量 7萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章