先来看一段使用Thread的代码,简单而常见
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// 开启线程,启动循环任务
new Thread() {
@Override
public void run() {
while(true) {
SystemClock.sleep(1000);
}
}
}.start();
}
}
Ok,想一下,如果用户旋转了屏幕,这时会发生什么?
正常情况下,系统新创建一个横屏的Activity实例,销毁旧的Activity实例并释放相关资源。
但在这里,旧的Activity会长久的存在,直到发生OOM。为什么?
首先,它内部匿名的Thread实例会长久运行,不会被系统GC回收。Threads在Java中是GC roots,意味着 Dalvik Virtual Machine (DVM) 会为所有活跃的threads在运行时系统中保持一个硬引用,这会导致threads一直处于运行状态,垃圾收集器将永远不可能回收它。
其次,非静态内部类会持有外部类的引用。Thread会长久地持有Activity的引用,使得系统无法回收Activity和它所关联的资源和视图。
针对后者,MainActivity的内存泄漏问题,只需把非静态的Thread匿名类定义成静态的内部类就行了。
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);
}
}
}
}
现在新创建的Thread不会持有MainActivity的一个隐式引用,当手机屏幕旋转时不会阻止垃圾回收器对旧MainActivity的回收工作。
而在第一个问题中,一旦有一个新的Activity创建,那么就有一个Thread永远得不到清理回收,发生内存泄漏。
出于这个原因,我们应当为thread添加一个结束的逻辑,如下代码所示
public class MainActivity extends Activity {
private MyThread mThread;
@Override
protectedvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
exampleThree();
}
privatevoid 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
protectedvoid onDestroy() {
super.onDestroy();
mThread.close();
}
}
在上述的代码中,当Activity结束销毁时在onDestroy()方法中结束了新创建的线程,保证了thread不会发生泄漏。但是如果你想在手机配置发生改变时,保持原有的线程而不重新创建的话,你可以考虑使用fragment来保留原有的线程,以备下一次使用。
结论
-
使用静态内部类/匿名类,不要使用非静态内部类/匿名类。非静态内部类/匿名类会隐式的持有外部类的引用,外部类就有可能发生泄漏。而静态内部类/匿名类不会隐式的持有外部类引用,外部类会以正常的方式回收,如果你想在静态内部类/匿名类中使用外部类的属性或方法时,可以显式的持有一个弱引用。
</br> -
不要以为Java永远会帮你清理回收正在运行的threads。在上面的代码中,我们很容易误以为当Activity结束销毁时会帮我们把正在运行的thread也结束回收掉,但事情永远不是这样的!Java threads会一直存在,只有当线程运行完成或被杀死掉,线程才会被回收。所以我们应该养成为thread设置退出逻辑条件的习惯。
</br> - 适当的考虑下是否应该使用线程。Android应用框架设计了许多的类来简化执行后台任务,我们可以使用与Activity生命周期相关联的Loaders来执行简短的后台查询任务。如果一个线程不依赖与Activity,我们还可以使用Service来执行后台任务,然后用BroadcastReceiver来向Activity报告结果。另外需要注意的是本文讨论的thread同样使用于AsyncTasks,AsyncTask同样也是由线程来实现,只不过使用了Java5.0新增并发包中的功能,但同时需要注意的是根据官方文档所说,AsyncTask适用于执行一些简短的后台任务