在前面一篇博客《Android全面解析Handler》一文中,我们认识了Handler的异步通信机制,同时也提到过Handler如果使用不慎将会导致内存泄露。今天主要来讲述一下Handler的内存泄露场景可能存在的场景以及解决方案。
场景一:直接传递外部类引用到静态内部类使用,导致静态内部类间接持有外部类的引用
举个栗子,在一个静态内部类我们想访问外部类的成员属性,怎么办?可不可以直接访问了,答案当然是不想的,如下所示,我们的代码提示是会直接报错的:
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
static class Runnable1 implements Runnable {
@Override
public void run() {
System.out.println("i=" + i);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
那是因为就是这个类被编译的时候,static
变量会被初始化有且只有一次。但非静态的变量是对象被创建的时候(即new的时候)才存在的,所以static在第一次初始化的时候,这些非静态变量根本不存在,所以也就引用不了了。听到这里,有人会说,那你把外部类的成员属性改成static
不就可以访问了。当然,如果把i这个成员变量改成static
当然是可以访问到的,但是有没有一种方式是不用改变外部类成员属性,就可以使得静态内部类可以访问的了,当然是有的,且看下面的代码:
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
/**
* 如果直接传递外部类引用则依然会造成内存泄露
*/
static class Runnable2 implements Runnable {
//但是不能直接将外部类引用传递进来,因为虽然可以访问外部类的成员属性,但是还是会造成内存泄露
TestActivity activity;
public Runnable2(TestActivity activity) {
this.activity = activity;
}
@Override
public void run() {
System.out.println(activity.i);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
这种方式就可以实现,但是,同时也带来问题,那就是导致Handler
会出现内存泄露。因为,直接传递外部类引用而且还是强引用。大家都知道,在四大引用类型里面,强引用是最容易导致内存泄漏的。这就是Handler
的内存泄露的其中一个场景。
出现了内存泄露,就是需要我们必须处理的问题,细心的同学就回发现,既然有四大引用类型,是不是换一种引用方式就可以解决了,那么选用那种引用最合适了,那么就需要我们熟练这四种引用类型的特征,如果你不还不熟悉Java
对象的四种引用:《强引用、软引用、弱引用和虚引用》
的特性,建议你先查看我的《Java对象的四种引用》一文,这里不在啰嗦。下面直接给出解决方案
解决方案
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
/**
* 正确解决方案:使用弱引用,即静态内部类持有外部类的弱引用
*/
static class Runnable3 implements Runnable {
/**
* 注意这里应该使用WeakReference,而不是SoftReference,
* 虽然使用SoftReference也不会造成OOM,但是当我们退出Activity时是希望Activity尽快被回收的,
* 所以使用WeakReference更合适,因为被WeakReference关联的对象在GC执行时会被直接回收,
* 而对于SoftReference关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。
*/
WeakReference<TestActivity> activityWeakReference;
public Runnable3(TestActivity activity) {
this.activityWeakReference = new WeakReference<TestActivity>(activity);
}
@Override
public void run() {
if (activityWeakReference.get() != null) {
int i = activityWeakReference.get().i;
System.out.println("i=" + i);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
}
}
我们只需要将强引用改为弱引用即可,使用弱引用,即静态内部类持有外部类的弱引用。至于为何不适用其他引用类型,我在代码里已经给出了想想的注释。
场景二:内部类和匿名内部类持有外部类的引用:
这种内部类和匿名内部类持有外部类的引用导致内存泄漏最常见的莫过于mHandler.postDelayed()
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
/**
* Handler的内存泄露场景
*/
mHandler.postDelayed(new Runnable() {//这里创建了一个匿名内部类对象
@Override
public void run() {
//匿名内部类对象持有外部类的引用(即持有Activity的引用),所以可以直接访问外部类的的成员属性i
//所以会造成内存泄露
System.out.println("i=" + i);
}
}, 1_000);
}
}
为什么这样使用会造成Handler
内存泄漏了?那是因为非静态内部类持有外部类的引用,所以外部类的内存资源一直得不到回收,就可能会造成内存泄漏。知道了原因,下面就给出对应的解决方案。
解决方案一:
在onDestroy()
方法里调用 mHandler.removeCallbacksAndMessages(null)
;将消息队列里的Message
全部移除:
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
/**
* Handler的内存泄露场景
*/
mHandler.postDelayed(new Runnable() {//这里创建了一个匿名内部类对象
@Override
public void run() {
//匿名内部类对象持有外部类的引用(即持有Activity的引用),所以可以直接访问外部类的的成员属性i
//所以会造成内存泄露
System.out.println("i=" + i);
}
}, 1_000);
//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除
}
@Override
protected void onDestroy() {
super.onDestroy();
//解决方案1:在onDestroy()方法里调用 mHandler.removeCallbacksAndMessages(null);将消息队列里的Message全部移除
mHandler.removeCallbacksAndMessages(null);
}
}
解决方案二:
使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用:
//解决方案2:使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用
mHandler.postDelayed(new Runnable3(this), 10_000);
完整代码如下:
public class TestActivity extends AppCompatActivity {
private int i = 10;//成员属性i
private Handler mHandler = new Handler();
/**
* 正确解决方案:使用弱引用,即静态内部类持有外部类的弱引用
*/
static class Runnable3 implements Runnable {
/**
* 注意这里应该使用WeakReference,而不是SoftReference,
* 虽然使用SoftReference也不会造成OOM,但是当我们退出Activity时是希望Activity尽快被回收的,
* 所以使用WeakReference更合适,因为被WeakReference关联的对象在GC执行时会被直接回收,
* 而对于SoftReference关联的对象,GC不会直接回收,而是在系统将要内存溢出之前才会触发GC将这些对象进行回收。
*/
WeakReference<TestActivity> activityWeakReference;
public Runnable3(TestActivity activity) {
this.activityWeakReference = new WeakReference<TestActivity>(activity);
}
@Override
public void run() {
if (activityWeakReference.get() != null) {
int i = activityWeakReference.get().i;
System.out.println("i=" + i);
}
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_test);
//解决方案2:使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用
mHandler.postDelayed(new Runnable3(this), 10_000);
}
}
总结
以上俩种场景就是Handler
可能造成内存泄露场景的原因,同时我也给出了解决方案。总之一句话,在使用Handler
时我们要牢记如下俩条铁的规律,就可以避免使用Handler
造成内存泄露。
- 使用静态内部类,静态内部类不会持有外部类的引用,而内部类和匿名内部类会持有外部类的引用。所以外部类的内存资源一直得不到回收,就可能会造成内存泄漏。
- 属性Java对象的四种引用:强引用、软引用、弱引用和虚引用,明确各种引用的特性和使用特点就可以避免内存泄漏。