内存泄漏
Handler内存泄漏分析
public class MainActivity extends AppCompatActivity {
private final int MESSAGE_WHAT = 10000;
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000);
}
}
在启动时,用Handler发空消息,然后在处理消息里面又发空消息。我们启动MainActivity 之后,1s之内点返回,然后在Profile里面点多次回收,按理说MainActivity 应该被GC回收掉,没有MainActivity 的实例。 但是实际上,如下图所示,还有一个MainActivity 实例。
可以看到,我截取的部分是点了很多次GC的,很多个垃圾桶的标志。但是这个MainActivity仍然有一个实例,说明MainActivity已经泄露了。
泄漏分析
这个按钮(dump the heap)能自动截取一段内存信息,生成一个文件。
生成完点这里保存到磁盘中,后缀名是hprof
然后使用sdk的工具hprof-conv,转一下,转成mat工具可以使用的。
.\hprof-conv.exe .\test1.hprof .\test2.hprof
prof-conv xxxxx.hprof yyyyy.hprof,其中xxxxx.hprof为原始文件,yyyyy.hprof为转换过后的文件。
使用mat工具打开test2.hprof文件。Mat:下载地址
打开之后,点一下下图位置
在ClassName的地方输入MainActivity,然后回车,找到MainActivity的所有实例。
然后排除软、若、虚引用,也就是只留强引用。
然后mat会打开新的标签页,用来展示这个MainActivity的引用信息
从图中我们可以看到MainActivity被MainActivity$1引用,然后一层一层到上面。哦了,分析到这里,我们也能从证据上看出来MainActivity因为MainActivity$1的强引用导致内存泄漏了。
理论分析
首先非静态内部类持有外部类引用,其次Handler发送Message,会把本身交给Message,Message放到Looper的Queue中。倒过来推就是,Looper中引用Queue,Queue引用Message,Message引用Handler,Handler引用MainActivity。所以MainActivity内存泄漏了。
解决办法
static修饰
private static final int MESSAGE_WHAT = 10000;
static Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000);
}
};
经过垃圾回收,MainActivity已经被回收掉了,没有MainActivity的实例了。
缺点:static声明过多, 会造成类加载速度变慢,因为static是跟着类一起加载的。
软引用(这个在非静态内部类无法控制)
在Handler这个例子中没法测试,但是其它的可以试试的。
缺点:何时被回收不确定,如果在回收之后还需要用,就比较麻烦了。
销毁Activity时移除消息
public class MainActivity extends AppCompatActivity {
private final int MESSAGE_WHAT = 10000;
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 3 * 1000);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeMessages(MESSAGE_WHAT);
}
在onDestroy回调方法中,移除Message,这样引用链就会在Queue–Message这里断开,下面的对象就可以回收了。从截图中可以看到不论MainActivity还是Handler都被正确回收了。
缺点:如果Handler写的比较多,这样的做法导致代码量比较多。
总结
内存泄漏的原因就是 短生命周期的实例,被长生命周期的实例引用,导致短生命周期的实例无法被回收。具体如何解决内存泄漏问题,不可一概而论,具体问题,具体分析,如果上面的例子中,Handler处理的事情很简单,那么即使MainActivity销毁,只要Handler的事情做完了,还是可以正常回收的。
public class MainActivity extends AppCompatActivity {
private static final String TAG = MainActivity.class.getSimpleName();
private int MESSAGE_WHAT = 10000;
Handler handler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
super.handleMessage(msg);
Log.d(TAG, "handleMessage: 我收到消息了");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler.sendEmptyMessageDelayed(MESSAGE_WHAT, 5 * 1000);
}
@Override
protected void onDestroy() {
super.onDestroy();
}
}
如上所示,Handler处理的事情很简单, 最终也是没有内存泄漏的。
所以没有绝对的解决方法,具体问题,具体分析