[关闭]
@linux1s1s 2019-02-14T12:00:27.000000Z 字数 2817 阅读 3220

Android 内存泄露 (二)

AndroidMemory 2015-04


潜藏隐患的Thread

考虑以下的代码:

  1. /**
  2. * Example illustrating how threads persist across configuration
  3. * changes (which cause the underlying Activity instance to be
  4. * destroyed). The Activity context also leaks because the thread
  5. * is instantiated as an anonymous class, which holds an implicit
  6. * reference to the outer Activity instance, therefore preventing
  7. * it from being garbage collected.
  8. */
  9. public class MainActivity extends Activity {
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. exampleOne();
  14. }
  15. private void exampleOne() {
  16. new Thread() {
  17. @Override
  18. public void run() {
  19. while (true) {
  20. SystemClock.sleep(1000);
  21. }
  22. }
  23. }.start();
  24. }
  25. }

当configuration改变的时候(典型的比如旋转屏幕)会导致Activity销毁和重建,你很容易想到,Android会清理内存,并且回收正在运行的线程和与之相关的内存。事实上,并非如此。反而是每当configuration改变一次,内存便会泄露一次,而由此引发的内存泄露会导致Android性能显著降低,在低配置Android手机甚至会出现ANR。
如果你读过Android 内存泄露-Handlers这篇文章,应该很容易发现内存泄露发生在哪里,显然Thread匿名内部类持有MainActivity的引用,而这个匿名内部类始终存活无论它的外部类是神马状态,而这个外部类恰恰是Activity,所以这个Activity的整个视图层次结构及其所有资源都将无法被GC回收。
为了验证上面的说法,重复多次旋转屏幕,此时通过 Eclipse Memory Analyzer(如果你读过Android内存分析(一)这篇文章)应该能得到类似下面这个图:

此处输入图片的描述

从上图可以看出,每次旋转屏幕都会泄露Activity的整个视图层次结构及其所有资源。

简单优化的Thread

  1. /**
  2. * This example avoids leaking an Activity context by declaring the
  3. * thread as a private static inner class, but the threads still
  4. * continue to run even across configuration changes. The DVM has a
  5. * reference to all running threads and whether or not these threads
  6. * are garbage collected has nothing to do with the Activity lifecycle.
  7. * Active threads will continue to run until the kernel destroys your
  8. * application's process.
  9. */
  10. public class MainActivity extends Activity {
  11. @Override
  12. protected void onCreate(Bundle savedInstanceState) {
  13. super.onCreate(savedInstanceState);
  14. exampleTwo();
  15. }
  16. private void exampleTwo() {
  17. new MyThread().start();
  18. }
  19. private static class MyThread extends Thread {
  20. @Override
  21. public void run() {
  22. while (true) {
  23. SystemClock.sleep(1000);
  24. }
  25. }
  26. }
  27. }

此时线程不在持有外部类Activity的引用,所以每次旋转屏幕的时候GC很容易回收Activity的整个视图层次结构及其所有资源。

上面的优化其实并不推荐,由于Thread位于GC根部,DVM会和所有的活动线程保持hard references关系,所以运行中的Thread绝不会被GC无端回收了。所以,无论在任何情况下,你都必须对线程加入取消机制。就像下面这样:

推荐优化的Thread

  1. /**
  2. * Same as example two, except for this time we have implemented a
  3. * cancellation policy for our thread, ensuring that it is never
  4. * leaked! onDestroy() is usually a good place to close your active
  5. * threads before exiting the Activity.
  6. */
  7. public class MainActivity extends Activity {
  8. private MyThread mThread;
  9. @Override
  10. protected void onCreate(Bundle savedInstanceState) {
  11. super.onCreate(savedInstanceState);
  12. exampleThree();
  13. }
  14. private void exampleThree() {
  15. mThread = new MyThread();
  16. mThread.start();
  17. }
  18. /**
  19. * Static inner classes don't hold implicit references to their
  20. * enclosing class, so the Activity instance won't be leaked across
  21. * configuration changes.
  22. */
  23. private static class MyThread extends Thread {
  24. private boolean mRunning = false;
  25. @Override
  26. public void run() {
  27. mRunning = true;
  28. while (mRunning) {
  29. SystemClock.sleep(1000);
  30. }
  31. }
  32. public void close() {
  33. mRunning = false;
  34. }
  35. }
  36. @Override
  37. protected void onDestroy() {
  38. super.onDestroy();
  39. mThread.close();
  40. }
  41. }

上面的代码中,在Activity的onDestroy生命周期中关闭线程,已确保不会因为Thread引起内存泄露。

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注