1. 程式人生 > >android 記憶體洩漏小結

android 記憶體洩漏小結

什麼是記憶體洩漏

記憶體洩漏是當程式不再使用到的記憶體時,釋放記憶體失敗而產生了無用的記憶體消耗。記憶體洩漏並不是指物理上的記憶體消失,這裡的記憶體洩漏是值由程式分配的記憶體但是由於程式邏輯錯誤而導致程式失去了對該記憶體的控制,使得記憶體浪費

怎樣會導致記憶體洩漏

資源物件沒關閉造成的記憶體洩漏,如查詢資料庫後沒有關閉遊標cursor
構造Adapter時,沒有使用 convertView 重用
Bitmap物件不在使用時呼叫recycle()釋放記憶體
物件被生命週期長的物件引用,如activity被靜態集合引用導致activity不能釋放

記憶體洩漏有什麼危害

記憶體洩漏對於app沒有直接的危害,即使app有發生記憶體洩漏的情況,也不一定會引起app崩潰,但是會增加app記憶體的佔用。記憶體得不到釋放,慢慢的會造成app記憶體溢位。所以我們解決記憶體洩漏的目的就是防止app發生記憶體溢位

記憶體洩漏檢測工具

LeakCanary github地址:https://github.com/square/leakcanary

例項

  1. 新建執行緒引起的Activity記憶體洩漏
public class Activity6 extends AppCompatActivity {

  @Override
  protected void
onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_6); findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { finish(); } }); new Thread(new
Runnable() { @Override public void run() { try {<br> //模擬耗時操作 Thread.sleep( 15000 ); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } }

執行上面的程式碼後,點選finish按鈕,過一會兒發生了記憶體洩漏的問題。
Activity6銷燬,但是Activity6裡面的執行緒還在執行,匿名內部類Runnable物件引用了Activity6的例項,導致Activity6所佔用的記憶體不能被GC及時回收
改進
Runnable改為靜態非匿名內部類即可

public class Activity6 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_6);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new Thread( new MyRunnable()).start();

  }

  private static class MyRunnable implements Runnable {

    @Override
    public void run() {
      try {
        Thread.sleep( 15000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
  }

}
  1. Activity新增監聽器造成Activity記憶體洩漏
public class LeakActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NastyManager.getInstance().addListener(this);
  }
}

這個是在開發中經常會犯的錯誤,NastyManager.getInstance() 是一個單例,當我們通過 addListener(this) 將 Activity 作為 Listener 和 NastyManager 繫結起來的時候,不好的事情就發生了。

如何改進?

想要修復這樣的 Bug,其實相當簡單,就是在你的 Acitivity 被銷燬的時候,將他和 NastyManager 取消掉繫結就好了。

public class LeakActivity extends Activity {
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    NastyManager.getInstance().addListener(this);
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    NastyManager.getInstance().removeListener(this);
  }
}
  1. Handler 匿名內部類造成記憶體溢位
public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;

  private final Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      Log.d("mmmmmmmm" , "handler " + msg.what ) ;
    }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    new Thread(new Runnable() {
      @Override
      public void run() {
        handler.sendEmptyMessage( MESSAGECODE ) ;
        try {
          Thread.sleep( 8000 );
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        handler.sendEmptyMessage( MESSAGECODE ) ;
      }
    }).start() ;

  }
}

這段程式碼執行起來後,立即點選 finish 按鈕,通過檢測,發現 HandlerActivity 出現了記憶體洩漏。當Activity finish後,延時訊息會繼續存在主執行緒訊息佇列中8秒鐘,然後處理訊息。而該訊息引用了Activity的Handler物件,然後這個Handler又引用了這個Activity。這些引用物件會保持到該訊息被處理完,這樣就導致該Activity物件無法被回收,從而導致了上面說的 Activity洩露。Handler 是個很常用也很有用的類,非同步,執行緒安全等等。如果有下面這樣的程式碼,會發生什麼呢? handler.postDeslayed ,假設 delay 時間是幾個小時… 這意味著什麼?意味著只要 handler 的訊息還沒有被處理結束,它就一直存活著,包含它的 Activity 就跟著活著。我們來想辦法修復它,修復的方案是 WeakReference ,也就是所謂的弱引用。垃圾回收器在回收的時候,是會忽視掉弱引用的,所以包含它的 Activity 會被正常清理掉。
如何避免
使用靜態內部類
使用弱引用
修改後程式碼是這樣的。

public class HandlerActivity extends AppCompatActivity {

  private final static int MESSAGECODE = 1 ;
  private static Handler handler ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_handler);

    findViewById( R.id.finish ).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //建立Handler
    handler = new MyHandler( this ) ;

    //建立執行緒並且啟動執行緒
    new Thread( new MyRunnable() ).start();
  }

  private static class MyHandler extends Handler {
    WeakReference<HandlerActivity> weakReference ;

    public MyHandler(HandlerActivity activity ){
      weakReference = new WeakReference<HandlerActivity>( activity) ;
    }

    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      if ( weakReference.get() != null ){
        // update android ui
        Log.d("mmmmmmmm" , "handler " + msg.what ) ;
      }
    }
  }

  private static class MyRunnable implements Runnable {

    @Override
    public void run() {
      handler.sendEmptyMessage( MESSAGECODE ) ;
      try {
        Thread.sleep( 8000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      handler.sendEmptyMessage( MESSAGECODE ) ;
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //如果引數為null的話,會將所有的Callbacks和Messages全部清除掉。
    handler.removeCallbacksAndMessages( null );
  }
}

解決了Handler,Runnable 引用Activity例項從而導致記憶體洩漏的問題
在OnDestory()方法裡,取消執行緒
handler.removeCallbacksAndMessages( null );

  1. AsyncTask造成記憶體洩漏
public class Activity2 extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });


    new AsyncTask<String,Integer,String>(){

      @Override
      protected String doInBackground(String... params) {
        try {
          Thread.sleep( 6000 );
        } catch (InterruptedException e) {
        }
        return "ssss";
      }

      @Override
      protected void onPostExecute(String s) {
        super.onPostExecute(s);
        Log.d( "mmmmmm activity2 " , "" + s ) ;
      }

    }.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "" ) ;

  }
}

activity中建立了一個匿名類AsyncTask,匿名類和非靜態內部類相同,會持有外部類物件,這裡也就是activity,因此如果你在Activity裡宣告且例項化一個匿名的AsyncTask物件,則可能會發生記憶體洩漏,如果這個執行緒在Activity銷燬後還一直在後臺執行,那這個執行緒會繼續持有這個Activity的引用從而不會被GC回收,直到執行緒執行完成。

怎麼解決?

自定義靜態AsyncTask類A
syncTask的週期和Activity週期保持一致。也就是在Activity生命週期結束時要將AsyncTask cancel掉

public class AsyncTaskActivity extends AppCompatActivity {

  private static MyTask myTask ;
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_asynctask);

    findViewById( R.id.finish).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    myTask = new MyTask() ;
    myTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR , "") ;

  }

  private static class MyTask extends AsyncTask{

    @Override
    protected Object doInBackground(Object[] params) {
      try {
        //模擬耗時操作
        Thread.sleep( 15000 );
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      return "";
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消非同步任務
    if ( myTask != null ){
      myTask.cancel(true ) ;
    }
  }
}
  1. Timer Tasks 造成記憶體洩漏
public class TimerActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //開始定時任務
    timer();
  }

  void timer(){
    new Timer().schedule(new TimerTask() {
      @Override
      public void run() {
        while(true);
      }
    },1000 ); // 1秒後啟動一個任務
  }
}

為什麼?

怎麼解決?

在適當的時機進行Cancel。
TimerTask用靜態內部類
注意:在網上看到一些資料說,解決TimerTask記憶體洩漏可以使用在適當的時機進行Cancel。經過測試,證明單單使用在適當的時機進行Cancel , 還是有記憶體洩漏的問題。所以一定要用靜態內部類配合使用。

public class TimerActivity extends AppCompatActivity {

  private TimerTask timerTask ;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_2);

    findViewById( R.id.finish2).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        finish();
      }
    });

    //開始定時任務
    timer();
  }

  void timer(){
    timerTask = new MyTimerTask() ;
    new Timer().schedule( timerTask ,1000 ); // 1秒後啟動一個任務
  }

  private static class MyTimerTask extends TimerTask{

    @Override
    public void run() {
      while(true){
        Log.d( "ttttttttt" , "timerTask" ) ;
      }
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();

    //取消定時任務
    if ( timerTask != null ){
      timerTask.cancel() ;
    }
  }
}

推薦文章
java之內部類(InnerClass)—-非靜態內部類、靜態內部類、區域性內部類、匿名內部類