首先看看内存泄露与内存溢出的概念:
1、内存泄漏:
当出现对Activity、View或drawable等类的对象长期持有无用的引用,就会造成被引用的对象无法在GC时回收,而是长期占用堆空间,此时就会发生内存泄漏。
简单来说,就是保留下来却永远不再使用的对象引用。
2、内存溢出:
如果应用程序在消耗光了所有的可用堆空间(16M到48M),那么再试图在堆上分配新对象时就会引起OOM(Out Of Memory Error)异常,此时应用程序就会崩溃退出。
3、两者的区别:
简单的说,就是内存溢出是占用内存太大,超过了其可以承受的范围;
内存泄漏是回收不及时甚至是没有被回收,而在推空间中产生的许多无用的引用。
于是过多的内存泄漏就会导致内存溢出,从而迫使程序崩溃退出。
下面重点来谈谈android中有关内存泄露的问题。
虽然Java有专门的GC来回收不再使用的对象,但是我们不能完全依靠GC来彻底回收对象,在编码的过程中如不遵守一定的规则就会造成内存泄露。主要有以下三种:
1,静态变量持有对象,特别是生命周期比整个应用短的对象
2,非静态内部类隐式持有外部类对象,特别是内部类生命周期比外部类长
3,资源打开未关闭,如文件,数据库等
一, 静态变量持有对象,特别是生命周期比整个应用短的对象
泄漏 activity 最简单的方法就是在 activity 类中定义一个 static 变量,并且将其指向一个运行中的 activity 实例。如果在 activity 的生命周期结束之前,没有清除这个引用,那它就会泄漏了。这是因为activity(例如 MainActivity) 的类对象是静态的,一旦加载,就会在 APP 运行时一直常驻内存,因此如果类对象不卸载,其静态成员就不会被垃圾回收。
二非静态内部类隐式持有外部类对象,特别是内部类生命周期比外部类长。
1 handler内存泄露
最常见的就是在actvity中使用handler。创建非静态内部的时候,该内部类默认会持有外部类的引用,如果外部类的生命周期比较短,那么内部类就不会释放外部类对象的引用,这就造成了内存泄露。
public class HandlerActivity extends Activity{
private final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
// ...
}
};
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mHandler.sendMessageDelayed(Message.obtain(), 1000);
finish();
}
原因分析:
1. Handler 的生命周期与Activity 不一致。当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
2. handler 引用 Activity 阻止了GC对Acivity的回收。
当Activity finish后,meaasge会继续存在UI线程的messagequeue中,。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的Activity泄露。
publicboolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = newRuntimeException(
this + "sendMessageAtTime() called with no mQueue");
Log.w("Looper",e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg,uptimeMillis);
}
修改方法:
· 使用显形的引用,1.静态内部类。 2. 外部类
· 使用弱引用 2.WeakReference
static class ExerciseHandler extends Handler{ private SoftReference<ExerciseActivity> exerciseActivitySoftReference = null; public ExerciseHandler(ExerciseActivity exerciseActivity){ exerciseActivitySoftReference = new SoftReference<ExerciseActivity>(exerciseActivity); } @Override public void handleMessage(Message msg) { ExerciseActivity exerciseActivity = exerciseActivitySoftReference.get(); if(null != exerciseActivity){ super.handleMessage(msg); switch (msg.what) { case MSG_XX: exerciseActivity.***; break; default: break; } } } public void onDestroy() { mHandler.removeMessages(MESSAGE_1); mHandler.removeMessages(MESSAGE_2); mHandler.removeMessages(MESSAGE_3); // ... ... mHandler.removeCallbacks(mRunnable); // ... ... }
三.资源打开未关闭,如文件,数据库等
- IO操作后,没有关闭文件导致的内存泄露,比如Cursor、FileInputStream、FileOutputStream使用完后没有关闭,这种问题在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了;
- 自定义View中使用TypedArray后,没有recycle,这种问题也可以在Android Studio 2.0中能够通过静态代码分析检查出来,直接改善就可以了
四 Context 内存泄露
某些地方使用了四大组件的context,在离开这些组件后仍然持有其context导致的内存泄露,这种问题属于共识,在编写代码的过程中就应该按照规则来,使用Application的Context就可以解决这类内存泄露的问题了。
常见的解决办法:
通过工具检查程序运行后的内存泄露
通过上面的步骤,应用中的大部分内存泄露问题都能够得到解决,还有一些内存泄露,需要运行程序,分析运行后的内存快照来解决,比如注册之后没有反注册、类中的静态成员变量导致的内存泄露、SDK中的内存泄露等。解决这类问题可以分两步进行:
通过内存泄露检测工具先定位是哪有问题,内存泄露的检测有两种比较便捷的方式:1、一种是使用开源项目Leakcanary,需要添加到代码中,运行后生成分析结果;2、另一种方式是使用adb shell dumpsys meminfo packagename -d命令,在进入一个界面之前查看一遍Activity和View的数量,在退出这个界面之后再查看一遍Activity和View的数量,对比进入前和进入后Activity和View数量的变化情况,如果有差异,则说明存在内存泄露(在使用命令查看Activity和View的数量之前,记得手动触发GC)。