Avtivitys, Threads & Memory Leaks

Ref: http://www.androiddesignpatterns.com/2013/04/activitys-threads-memory-leaks.html


       安卓開發中一個難點就是activity生命週期裏的長時間運行任務可能造成的內存泄漏。看下面一段代碼:

/**
 * Example illustrating how threads persist across configuration
 * changes (which cause the underlying Activity instance to be
 * destroyed). The Activity context also leaks because the thread
 * is instantiated as an anonymous class, which holds an implicit
 * reference to the outer Activity instance, therefore preventing
 * it from being garbage collected.
 */
public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleOne();
  }

  private void exampleOne() {
    new Thread() {
      @Override
      public void run() {
        while (true) {
          SystemClock.sleep(1000);
        }
      }
    }.start();
  }
}
        當配置發生變化時,造成整個ACTIVITY被摧毀重造,很容易以爲安卓會進行清理然後重新聲明新的內存給ACTIVITY和它的線程。但是不是這樣的。兩者都會造成泄漏,這回導致嚴重的性能問題。


怎麼泄漏一個ACTIVITY

         第一個內存泄漏應該很明顯,讀過以前寫的內部類匿名類泄漏的文章就知道,非靜態的內部/匿名類會隱含引用外部ACTIVITY,這樣這個ACTIVITY就不會被GC。而ACTIVITY對象又引用了整個VIEW結構和所有它的資源,所有一旦泄漏就會造成大量內存泄漏。

         

http://www.androiddesignpatterns.com/assets/images/posts/2013/04/15/activity-leak.png

10次 orientation 變化後內存狀況;


解決方案,用靜態內部類線程:

/**
 * This example avoids leaking an Activity context by declaring the 
 * thread as a private static inner class, but the threads still 
 * continue to run even across configuration changes. The DVM has a
 * reference to all running threads and whether or not these threads
 * are garbage collected has nothing to do with the Activity lifecycle.
 * Active threads will continue to run until the kernel destroys your 
 * application's process.
 */
public class MainActivity extends Activity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleTwo();
  }

  private void exampleTwo() {
    new MyThread().start();
  }

  private static class MyThread extends Thread {
    @Override
    public void run() {
      while (true) {
        SystemClock.sleep(1000);
      }
    }
  }
}
這樣新線程不會持有外部ACTIVITY的引用了,activity就可以順利被GC。


怎麼泄漏一個線程

下邊是線程泄漏的問題。所有激活狀態的java線程都在Dalvik虛擬機裏有強引用,所以不會被GC。因此使用後臺線程時一定要注意實施銷燬測策略。下邊給出一種解決方案:

/**
 * Same as example two, except for this time we have implemented a
 * cancellation policy for our thread, ensuring that it is never 
 * leaked! onDestroy() is usually a good place to close your active 
 * threads before exiting the Activity.
 */
public class MainActivity extends Activity {
  private MyThread mThread;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    exampleThree();
  }

  private void exampleThree() {
    mThread = new MyThread();
    mThread.start();
  }

  /**
   * Static inner classes don't hold implicit references to their
   * enclosing class, so the Activity instance won't be leaked across
   * configuration changes.
   */
  private static class MyThread extends Thread {
    private boolean mRunning = false;

    @Override
    public void run() {
      mRunning = true;
      while (mRunning) {
        SystemClock.sleep(1000);
      }
    }

    public void close() {
      mRunning = false;
    }
  }

  @Override
  protected void onDestroy() {
    super.onDestroy();
    mThread.close();
  }
}
上邊這段代碼在onDestroy()方法裏保證永遠不會異化的發生線程泄漏。

總結給出的建議:

  • 靜態內部類好過非靜態
  • JAVA永遠不會GC你的運行中的線程。給你的後臺線程設計關閉策略。
  • 考慮是否用Thread。查詢DB可以用LOADER,小任務用ASYNCTASK。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章