之前用AndroidStudio自带的代码分析出现的警告,说的是Handler导致内存泄露
Handler reference leaks
the Handler should be static or leaks might occur (null)
出现这个提示的原因是有个Handler是内部类,并且是运行在主线程中的,我就是在Activity里有个内部类的Handler
因为调用Handler的sendMessage之类的一些方法会调用下面的代码
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;//Handler被保存进msg的target中
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);//msg又会进入队列
}
原因解释:msg的target会保存Handler对象的引用,并且放入Looper中,如果Looper所在线程为主线程,如果Handler一直在主线程的Looper中,而Handler因为是内部类,所以Handler又持有外部类的对象,那么外部类将一直无法被GC回收
而代码分析也给出了解决方法:
the Handler should be static,就是将Handler改为静态内部类的意思,这样Handler就不持有外部类的对象了
还有另一种方法是java中常用的解决内存泄露的方法是使用虚引用或弱引用
但是我们回到原点,如果Handler是内部类,并且Looper所在线程为主线程,那么一定会造成内存泄露么
其实还有一个条件,那么就是Handler必须长时间在主线程的Looper中,一旦Handler不在Looper中,实际上Handler还是可以被回收的
我们将使用Android Studio自带的查看内存的工具:
public class SecondActivity extends Activity implements View.OnClickListener{
private static final int TEST = 1001;
private static final int TEST_LOOP = 1002;
private int [][] mFourMB = new int[4096][1024];
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_second);
initEvent();
}
private void initEvent(){
findViewById(R.id.bt_one).setOnClickListener(this);
findViewById(R.id.bt_loop).setOnClickListener(this);
}
private Handler mUiHandler = new Handler() {
@Override
public void dispatchMessage(Message msg) {
switch (msg.what) {
case TEST:
Toast.makeText(SecondActivity.this, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
break;
case TEST_LOOP:
Toast.makeText(SecondActivity.this, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
mUiHandler.sendEmptyMessageDelayed(TEST_LOOP, 5000);
break;
}
}
};
@Override
public void onBackPressed() {
this.finish();
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.bt_one:
mUiHandler.sendEmptyMessage(TEST);
break;
case R.id.bt_loop:
mUiHandler.sendEmptyMessage(TEST_LOOP);
break;
}
}
}
测试代码如上,主要是主要是使用发一次执行一次的
mUiHandler.sendEmptyMessage(TEST);
和发一次一直执行的:
mUiHandler.sendEmptyMessage(TEST_LOOP);
调用发送一次的执行一次的Handler时,内存从48M -> 16M,可顺利回收SecondActivity
调用发送一次的一直执行的Handler时,内存从48M -> 48M,不能顺利回收SecondActivity,
测试结果是符合预期的,那么,解决内部Handler类,导致的内存泄露也就很简单了,只需要在不需要Handler时,调用
mUiHandler.removeMessages(TEST_LOOP);
就可以了。
一般可以考虑放在Activity的onDestroy()方法内部。
如果不能及时removeMessage,那么还是尽量将MyHandler设置为静态内部类,并使用WeakReference的方式进行Activity的访问比较合理:
private static class MyHandler extends Handler{
WeakReference<SecondActivity> secondActivityWeakReference;
public MyHandler(SecondActivity secondActivity) {
this.secondActivityWeakReference = new WeakReference<SecondActivity>(secondActivity);
}
@Override
public void dispatchMessage(Message msg) {
SecondActivity secondActivity = secondActivityWeakReference.get();
if(secondActivity == null){
return;
}
switch (msg.what) {
case TEST:
Toast.makeText(secondActivity, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
break;
case TEST_LOOP:
Toast.makeText(secondActivity, "测试 " + mFourMB.length, Toast.LENGTH_SHORT).show();
secondActivity.mUiHandler.sendEmptyMessageDelayed(TEST_LOOP, 5000);
break;
}
}
}