一、先上代码
有如下代码:
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mText.setText("收到消息");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.inject(this);
mHandler.sendEmptyMessageDelayed(0, 5000);
finish();
}
在android studio中,lint工具会提示:
提示信息是:Handler类应该被静态,否则可能会发生内存泄露。
二、为什么会是这样呢?了解一下Handler:
1、当Android程序第一次创建的时候,在主线程同时会创建一个Looper对象。Looper实现了一个简单的消息队列,一个接着一个处理Message对象。程序框架所有主要的事件(例如:屏幕上的点击时间,Activity生命周期的方法等等)都包含在Message对象中,然后添加到Looper的消息队列中,一个一个处理。主线程的Looper存在整个应用程序的生命周期内。
2、当一个Handler对象在主线程中创建的时候,它会关联到Looper的 messagequeue 。Message添加到消息队列中的时候,Message会持有当前Handler引用,当Looper处理到当前消息的时候,会调用Handler.handleMessage(Message).
3、在java中,非静态 的内部类会隐式的持有当前类的一个引用。static的类则没有。
4、如果你执行了Handler的postDelayed()方法,该方法会将你的Handler装入一个Message,并把这条Message推到MessageQueue中,那么在你设定的delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
三、使用Handler导致内存泄露的解决方法:
方法一:通过程序逻辑来进行保护。
1.在关闭Activity的时候停掉你的后台线程。线程停掉了,就相当于切断了Handler和外部连接的线,Activity自然会在合适的时候被回收。
Handler.getLooper().quitSafely() 将线程安全退出
2.如果你的Handler是被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks()方法,把消息对象从消息队列移除就行了。
方法二:将Handler声明为静态类。
静态类不持有外部类的对象,所以你的Activity可以随意被回收。代码如下:
private static Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
mText.setText("收到消息");
}
};
但其实没这么简单。使用了以上代码之后,你会发现,由于Handler不再持有外部类对象的引用,导致程序不允许你在Handler中操作Activity中的对象了。所以你需要在Handler中增加一个对Activity的弱引用(WeakReference):
@InjectView(R.id.text)
static TextView mText;
private Handler mHandler = new MyHandler(this);
private static class MyHandler extends Handler {
WeakReference<Activity > mActivityReference;
public MyHandler(Activity activity) {
mActivityReference= new WeakReference<Activity>(activity);
}
@Override
public void handleMessage(Message msg) {
final Activity activity = mActivityReference.get();
if (activity != null) {
mText.setText("收到消息");
}
}
}
四、一个内存泄漏的例子:
public class MainActivity extends Activity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.hello_text);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("Done");
}
}, 80000);
}
}
这是一个基本的activity。在handler的post方法中我们加入了一个匿名的runnable,同时我将其执行延迟了整整80秒。我们运行这个程序,并且旋转几次手机,发现内存有好几个activity,并没有被GC掉。
$$从上图中我们可以看到其中一个对mainactivity的引用是来自this$0,this$0是什么呢?以下是关于this$0的解释:static的inner class里面都会有一个this$0的字段保存它的父对象。在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
在我们的代码中,匿名的runnable是一个非静态的内部类,因此他会使用this$0来保存MainActivity,然后runnable会继续被它的callback引用,而callback又接着被接下来一连串的message引用,这样主线程的引用就太他妈多了。 当Activity finish后,延时消息会继续存在主线程消息队列中80秒,然后处理消息,因此handler继续存在于内存中,而handler引用了Activity,在我们旋转手机的时候,Activity不停的重建和finish,导致多个activity的引用出现。
一旦将Runnable或者是Message 发送进handler,将保存一连串的引用了主线程(这里是MainActivity吧)的Message命令,直到message执行完。如果发送Runnable设置了延迟时间,那么至少在这段延迟时间内内存泄漏是肯定的,如果是直接发送,在Message比较大的情况下,也是有可能发生暂时的泄漏的。
解决方法一:使用static
public class MainActivity extends Activity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.hello_text);
mHandler.postDelayed(new DoneRunnable(mTextView), 80000);
}
private static final class DoneRunnable implements Runnable{
private final TextView textView;
public DoneRunnable(TextView textView){
this.textView = textView;
}
@Override
public void run() {
textView.setText("Done");
}
}
}
在最底下我们发现activity继续被DoneRunnable里面mTextView中的mContext引用着。看来在这种情况下,看来仅仅使用static并没有解决问题啊。还需要做点工作才行。
既然是因为mTextView引起的,那我们把mTextView换成弱引用好了,需要注意的,既然mTextView是弱引用,所以随时都可能为null,因此需要在使用前判断是否为空。
public class MainActivity extends Activity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.hello_text);
mHandler.postDelayed(new DoneRunnable(mTextView), 80000);
}
private static final class DoneRunnable implements Runnable{
private final WeakReference<TextView> weakReference;
public DoneRunnable(TextView textView){
weakReference = new WeakReference<TextView>(textView);
}
@Override
public void run() {
TextView textView = weakReference.get();
if (textView != null) {
textView.setText("Done");
}
}
}
}
总结上面的方法:
使用静态的内部类
对所有handler/Runnable中的变量都用弱引用。
但是这种方式代码很多,而且还必须得小心翼翼。
解决方法二:在onDestroy中清理掉所有Messages
Handler有个很方便的方法:removeCallbacksAndMessages,当参数为null的时候,可以清除掉所有跟次handler相关的Runnable和Message,我们在onDestroy中调用次方法也就不会发生内存泄漏了。
public class MainActivity extends Activity {
private Handler mHandler = new Handler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.hello_text);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("Done");
}
}, 80000);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
解决方法三:WeakHandler
WeakHandler使用起来和handler一模一样,但是他是安全的,你只需要把以前的Handler替换成WeakHandler就行了WeakHandler使用如下:
public class MainActivity extends Activity {
private WeakHandler mHandler = new WeakHandler();
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.hello_text);
mHandler.postDelayed(new Runnable() {
@Override
public void run() {
mTextView.setText("Done");
}
}, 80000);
}
@Override
protected void onDestroy() {
super.onDestroy();
mHandler.removeCallbacksAndMessages(null);
}
}
WeakHandler的实现原理:
WeakHandler的思想是将Handler和Runnable做一次封装,我们使用的是封装后的WeakHandler,但其实真正起到handler作用的是封装的内部,而封装的内部对handler和runnable都是用的弱引用。
>
WeakHandler的github地址