内存泄漏(Memory Leak)是指程序中己动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。
检测工具
其实,这个神秘又实用的工具就在我们的AS中,简单暴力,运行App就可以对其进行内存分析,它就是Android Profiler,打开步骤:View-Tools Window-Profiler,对于它的使用,建议朋友们查看这篇文章使用 Memory Profiler 查看 Java 堆和内存分配,因为网上很多文章都来源于此处,多看几遍才能体会到其中的乐趣!
手动GC的使用
1、创建一个新应用-打开Profiler-运行
2、新增一个HandlerActivity且在MainActivity中点击按钮打开该Activity-返回键
这段时间内,新增了一个实例HandlerActivity,它只有分配内存的时间,没有释放时间,虽然我们按了返回键,但是它的内存还没有被GC回收,所以也就没有释放内存。运行一段时间后,该对象还是没有得到释放
我们点击一下手动GC按钮看看,手动GC按钮在这个地方
这个时候就发现释放时间就出来了,有没有一种雨后天晴的快感,通过这个例子相信大家都能够使用该工具分析内存泄漏了。
小结:Activity执行onDestroy方法后,不是立刻释放内存的
Handler内存泄漏分析
1、匿名内部类写法
public class HandlerActivity extends AppCompatActivity {
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(0, 24 * 60 * 60 * 1000);
}
}
网上都说这个Handler匿名类持有外部类的引用会导致内存泄漏,我们踹一踹,打开HandlerActivity后,点击返回键,返回到MainActivity,并且点击手动GC按钮几次!
通过实验,我们得出的确如此,无论手动GC多少次,该对象占用的内存都不会得到释放,不过这个跟延迟时间有关,我们这里设置的延迟时间是一天,因此,一天内不管你怎么手动回收,系统都不会释放掉这块内存。如果我们把延时发送时间改为十秒,那么十秒内内存也不会得到释放,十秒后手动GC,这块内存就会释放掉了
小结:
1、匿名内部类持有外部类引用(外部类释放不了可证明)
2、使用匿名Handler不一定会导致内存泄漏(跟延迟时间有关)
3、延时发送时间越长,泄漏的时间也就越长
2、静态内部类写法
public class HandlerActivity extends AppCompatActivity {
private MLHandler handler = new MLHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(0, 24 * 60 * 60 * 1000);
}
static class MLHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
}
小结
1、静态内部类未持有外部类引用
2、HandlerActivity已释放,MLHandler不能释放(跟延迟时间有关)
3、外部类写法
HandlerActivity.java
public class HandlerActivity extends AppCompatActivity {
private MLHandler handler=new MLHandler();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_handler);
handler.sendEmptyMessageDelayed(0, 24 * 60 * 60 * 1000);
}
}
MLHandler.java
class MLHandler extends Handler {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
内存分析
HandlerActivity已经被手动GC,但是MLHandler还是无法得到释放,不知道是不是延迟太长导致?我们把延迟时间 handler.sendEmptyMessageDelayed(0, 24 * 60 * 60 * 1000),改成一秒handler.sendEmptyMessageDelayed(0, 1 * 60 * 1000),结果发现一秒内怎么点手动回收,MLHandler都不会释放,一秒后点击GC按钮就立马见效了!
Handler总结
1、匿名内部类写法会持有外部类引用,但是不一样会导致内存泄漏
2、是否发生内存泄漏与延迟时间有关
3、外部类写法和静态内部类一样,也会导致内存泄漏,和匿名内部类的区别在于不会持有外部类的引用
单例模式持有Activity内存泄漏分析
SingleInstanceActivity.java
public class SingleInstanceActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_single_instance);
SingleInstance.getInstance(this);
}
}
SingleInstance.java
public class SingleInstance {
private static Context context;
private static SingleInstance singleInstance;
private SingleInstance() {
}
public static SingleInstance getInstance(Context ctx) {
if (singleInstance == null) {
context = ctx;
singleInstance = new SingleInstance();
}
return singleInstance;
}
}
通过实验发现,手动GC好几次,但是因为单例的生命周期与应用一致,导致SingleInstance实例一直存在,SingleInstanceActivity释放不了是因为SingleInstance持有它的引用。
解决方案
由于单例生命周期与应用一致,因此我们把ApplicationContext给它就可以了
context = ctx;
换成
context = ctx.getApplicationContext();
通过上面这些例子,我们学会了使用工具Android Profiler对内存进行分析,并对常见的内存泄漏典型案例做了相关实验,相信朋友们已经有了深刻的认识,以后自己也可以对应用做内存泄漏检测了。