@linux1s1s
2019-02-14T12:00:27.000000Z
字数 2817
阅读 3244
AndroidMemory
2015-04
考虑以下的代码:
/**
* 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();
}
}
当configuration改变的时候(典型的比如旋转屏幕)会导致Activity销毁和重建,你很容易想到,Android会清理内存,并且回收正在运行的线程和与之相关的内存。事实上,并非如此。反而是每当configuration改变一次,内存便会泄露一次,而由此引发的内存泄露会导致Android性能显著降低,在低配置Android手机甚至会出现ANR。
如果你读过Android 内存泄露-Handlers这篇文章,应该很容易发现内存泄露发生在哪里,显然Thread匿名内部类持有MainActivity的引用,而这个匿名内部类始终存活无论它的外部类是神马状态,而这个外部类恰恰是Activity,所以这个Activity的整个视图层次结构及其所有资源都将无法被GC回收。
为了验证上面的说法,重复多次旋转屏幕,此时通过 Eclipse Memory Analyzer(如果你读过Android内存分析(一)这篇文章)应该能得到类似下面这个图:
从上图可以看出,每次旋转屏幕都会泄露Activity的整个视图层次结构及其所有资源。
/**
* 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的引用,所以每次旋转屏幕的时候GC很容易回收Activity的整个视图层次结构及其所有资源。
上面的优化其实并不推荐,由于Thread位于GC根部,DVM会和所有的活动线程保持hard references关系,所以运行中的Thread绝不会被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();
}
}
上面的代码中,在Activity的onDestroy生命周期中关闭线程,已确保不会因为Thread引起内存泄露。