内存泄漏定义(个人理解):
当一个对象本该被回收,但是因为有对象正在持有他的引用,导致他无法正常被回收,称为内存泄漏。例如一个匿名内部线程需要睡眠了10秒,我们在第5秒钟的时候推出了界面,此时线程还在执行,并且由于匿名内部类持有外部类的引用,所以导致了无法及时回收,造成内存泄露。(使用Leakcanary对上述情况做测试,发现在任务执行完以后就不报内存泄漏了,也就是说在执行完以后被回收了,那部分内存又被释放了)
本文中采用Leakcanary来直观的展示内存泄漏:
以下代码为从第二个界面返回到第一个界面时,LeakCanary进行检测的标志
D/LeakCanary: Watching instance of com.example.zhang.androidtestdemo01.leak.LeakTestActivity
几个知识点:
java中内部类持有外部类的引用(可通过反编译验证)
匿名内部类持有外部类的引用
静态内部类不持有外部类的引用
以下情况不会造成内存泄漏:
普通成员变量持有静态内部类实例的引用:
短周期对象持有了长周期对象的引用,在activity销毁时,其成员变量也会销毁,所以不会造成内存泄露。
可能造成内存泄漏的情况:
1.静态成员变量持有内部类的引用:
内部类持有外部类的引用,静态内部类又持有内部类的引用,所以相当于静态内部类(长生命周期)持有了外部类(短生命周期)的引用。由于静态成员变量的生命周期长于内部类的生命周期,造成外部类无法释放,造成内存泄漏。(静态成员变量或者方法随着类的销毁而销毁,成员变量随实例销毁而销毁)
2.匿名内部类所造成的内存泄漏:
匿名内部类持有外部类的引用,但是在界面退出的时候没有执行完成,导致推出后还持有activity的引用,造成泄露。
情况2的例子:
public class LeakTestActivity extends AppCompatActivity {
private static final String TAG ="TestLeak" ;
private MyAscnyTask myAscnyTask;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak_test);
myAscnyTask=new MyAscnyTask();
myAscnyTask.execute();
}
class MyAscnyTask extends AsyncTask {
@Override
protected String doInBackground(Void... params) {
try {
Log.d(TAG, "doInBackground: in");
Thread.sleep(15000);
Log.d(TAG, "doInBackground: finishAscnyTask");
} catch (InterruptedException e) {
Log.d(TAG, "doInBackground: catcherror");
e.printStackTrace();
}
return "";
}
}
}
结果:
其中按照log的记录来看,在返回到第一个界面以后,由于任务还没有执行完,确实是发生了内存泄漏,但是当任务执行完以后,引用就不再存在了,说明此时LeakTestActivity已经被回收了。但是这也不能掩盖它确实发生了内存泄漏的事实。
解决办法:
把匿名内部类改造成静态类,从而使其不持有外部类的引用。其中需要注意的是添加了对于需要引用外部的处理,使用WeakRefrence来指向外部的引用,避免持有外部的强引用,避免类不持有外部引用后,里面的成员变量持有外部引用。
改造后的代码如图:
public class LeakTestActivity extends AppCompatActivity {
private static final String TAG = "TestLeak";
private MyAscnyTask myAscnyTask;
private int mCount = 0;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_leak_test);
myAscnyTask = new MyAscnyTask(mCount);
myAscnyTask.execute();
}
static class MyAscnyTask extends AsyncTask {
private final WeakReference integerWeakReference;
public MyAscnyTask(Integer count) {
this.integerWeakReference = new WeakReference<>(count);
}
@Override
protected String doInBackground(Void... params) {
try {
Integer count = integerWeakReference.get();
Log.d(TAG, "doInBackground: in");
Thread.sleep(15000);
Log.d(TAG, "doInBackground: finishAscnyTask");
} catch (InterruptedException e) {
Log.d(TAG, "doInBackground: catcherror");
e.printStackTrace();
}
return "";
}
}
}
结果:
如图,页面销毁后没有造成内存泄漏,并且按照log来看,Leakcanary检测完毕后Ascntask的任务才执行完,说明退出LeakTestActivity后任务是还在运行的,而LeakTestActivity却没有内存泄漏(Leakcanary没有判定这种情况为内存泄漏)。
但是在正常情况下,我们退出一个Activity后,其中的动作都应该停止,所以对上述的问题也可以使用AscnTask.cancel(true)取消掉正在执行的任务,log显示也没有内存泄漏。
加上
@Override
protected void onDestroy() {
super.onDestroy();
myAscnyTask.cancel(true);
}
结果:
总结:
内部类和匿名内部类造成内存泄露的原因都是因为持有了外部类的引用,属于长周期对象持有了短周期对象的引用。
而解决这一问题的办法就是把他们改造成静态内部类,并且经常需要配合Weakrefrence来进行进一步的防止内存泄漏。
参考文章:
https://www.jianshu.com/p/65f914e6a2f8
https://www.jianshu.com/p/ed3f8b0f0471
https://blog.csdn.net/baidu_38477614/article/details/78874459
https://blog.csdn.net/carson_ho/article/details/79549417