根本原因
应该被回收的对象没有被回收,一直占用内存,导致内存泄漏,如果内存泄漏过多,有可能造成内存溢出(OOM)。
内存溢出
当内存泄漏过多时,应用需要的内存超过系统分配的内存限额时会导致内存溢出引发crash。
常见的内存泄漏
1、单例(静态变量引起)
如果单例需要持有一个上下文,而如果传入的上下文是短生命周期的activity,单例的生命周期跟app一致,是长生命周期的上下文持有短生命周期的实例。
解决办法:将单例持有的上下文换成applicationcontext。总之:传入的上下文生命周期不能短於单例的生命周期。比较常见,不发代码了。
2、非静态内部类或匿名内部类
在Java中非静态内部类或匿名内部类会隐式持有外部类实例。当匿名内部类进行耗时操作,在外部类已经销毁时还在持有,就会导致外部类无法被回收造成内存泄漏。
例如:
1、最经典的就是Handler导致的内存泄漏。 非静态内部类,做耗时操作一直持有外部类的实例,当外部类销毁时无法被回收。
2、创建的匿名线程类,隐式持有外部类的实例,线程做耗时操作,当外部类销毁时线程还未销毁,因持有外部类实例外部类无法被回收造成内存泄漏。
3、非静态内部类,在外部类中创建了静态实例,该情况类似于 5、静态变量。
**总之:**也就是说当非静态内部类或匿名内部类的存活时间长于持有的外部类的时间就可能出现内存泄漏。
示例1及解决方案:Handler使用静态内部类,并若引用外部activity。详见博客地址:
示例2:
匿名内部类创建线程,并在线程内执行耗时操作。
public class MainActivit extents Activity{
@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyThread leakClass = new MyThread();
MyThread.start();
}
class MyThread extends Thread {
@Override
public void run() {
while (true) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
//或者类似这样的匿名内部类
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
解决办法1:在Activity销毁时停止线程的运行,让它释放外部类。
class MyThread extends Thread {
@Override
public void run() {
while (flag) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//在onDestory方法中将flag置为false
@Override
protected void onDestory(){
super.onDestory();
flag = false;
}
解决办法2:将匿名内部类定义为静态内部类,因为静态内部类不会持有外部类的引用。
static class MyThread extends Thread {
@Override
public void run() {
while (flag) {
try {
Thread.sleep(60 * 60 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
解决方案总结:
- 通过逻辑判断,比如在外部类销毁时,停止耗时操作,让外部类在合适的时候被回收。
- 在外部类销毁时移除延时消息及runable。mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。
- 修改为静态内部类,静态内部类不会持有外部类的引用,外部类可以被随意回收,但也因此内部类无法操作外部类的对象,所以需要增加对外部类的弱引用。
3、广播、使用的三方框架未及时关闭
如广播的unregisterReceiver
private void unRegistReceiver(BroadcastReceiver receiver){
try {
getContext().unregisterReceiver(receiver);
} catch (IllegalArgumentException e) {
// ignore
}
}
4、IO流,数据库的cursor、ContentObserver等资源没有在Activity销毁时及时关闭,bitmap没有recycle(),导致资源无法会回收。
5、静态变量
当一个静态变量持有静态上下文时,持有的上下文销毁了,但由于还被静态变量持有导致不能被回收造成内存泄漏。例如:
private static Context context;
private static TextView textView;
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
context = this;
textView = new TextView(context);
findViewById(R.id.skip).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this,TestLeakActivity.class));
finish();
}
});
}
通过LeakCanary通知,MainActivity.context发生内存泄漏。
解决:不让静态变量持有短生命周期的上下文。
private Context context;
private TextView textView;
6、动画无限播放
当activity中有无限播放的属性动画,当activity销毁时,动画还在播放,此时view持有activity的引用导致无法被回收引发内存泄漏。
ObjectAnimator animator = ObjectAnimator.ofFloat(textView,"rotation",0,360);
animator.setRepeatCount(ValueAnimator.INFINITE);
animator.start();
以上程序在测试时,体会GC being lazy ,是有可能造成内存泄漏。
为保险起见,在activity销毁时停止动画的播放。
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
animator.cancel();
}
检测内存泄漏的工具
1、Android Profiler
AS自带的内存分析工具,一般用来检测是否发生了内存泄漏,如果要定位内存泄漏需要一个一个查找,比较麻烦。用法如下:
- 启动应用,在AS下方点击Android profiler
其他步骤见博客:https://blog.csdn.net/MarinaTsang/article/details/84786577
2、LeakCanary
见博客:https://blog.csdn.net/MarinaTsang/article/details/84786577
3、MAT(Memory analyst tool)
详见郭霖大神博客:https://blog.csdn.net/guolin_blog/article/details/42238633。非常详细。
另外Mac版的MAT可能打开提示错误,不要放在Download目录下即可。
.hrof 文件在Android profiler中导出即可。
看了很多篇文章,就不一一列举了,比如:
https://blog.csdn.net/wanghao200906/article/details/50426881