注:转载请注明来自Nemo, http://blog.csdn.net/nemo__
一、Android Studio内存泄露查找方法
1. 在Android Studio内,按Alt+6
,跳转到Android Monitor
编辑框。
2. 关注Memory
项内容,操作怀疑内存泄露的步骤。
3. 点击Memory
右边Initiate GC
后,Dump Java Heap
,会生成一个hprof文件。
4. 可以分析内存占用较大的对象,同时在hprof编辑框内有一个Analyzer Tasks
页,右上角点击开始
按钮,会在下方列出Detect Leaked Activities
。
5. 进一步分析Reference Tree
页中,最靠前的几项,基本能定位到泄露的地方。
Activity内存泄露
问题,也可以通过adb shell dumpsys meminfo xxx.package.xxx
后,观察下方Views
及Activities
数目,是否一直在增加。
二、内存泄露原因分析
1. 慎用static关键字,注意static Context.
public class MainActivity extends Activity{
public static Context mContext;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mContext = this;
}
}
Context对象为静态的,那么Activity就无法正常销毁,会常驻内存。
【解决方法】1.使用Application的Context;2.慎用static关键字。
2. 单例模式导致内存的泄漏
静态变量导致的内存泄漏太过明显,而单例模式带来的内存的泄漏容易被忽略。
public class SingleInstance {
private static volatile SingleInstance instance;
private Context context;
private SingleInstance(Context context) {
this.context = context;
}
public static SingleInstance getInstance(Context context) {
if(instance == null) {
synchronized(SingleInstance.class) {
if(instance == null) {
instance = new SingleInstance(context);
}
}
}
return instance;
}
}
上例中,传入给单例对象的context如果是Activity的context,而单例对象是一个static对象,其生命周期与应用程序是一致的(也就是说,只有应用程序进程被杀掉了,static对象才会被销),该SingleInstance单例静态对象持有当前Activity的context,当MainActivity退出时,由于instance还继续只有其context引用,对造成系统无法销毁该Activity,从而造成内存泄漏。
【解决方法】1.使用应用程序的getApplicationContext(),静态对象的生命周期与应用程序的生命周期一致,故此不会导致内存泄漏;2.持有传入的context的弱引用。
public class SingleInstance {
private WeakReference<Context> weakContext;
private SingleInstance(Context context)
{
weakContext = new WeakReference<Context>(context);
}
....
}
3. 属性动画导致的内存泄漏
从Android3.0开始,Google提供了属性动画,属性动画中有一类无限循环的动画,例:
mAnimator = ValueAnimator.ofFloat(DEGREE_START, DEGREE_END);
mAnimator.setDuration(DURATION);
mAnimator.setStartDelay(60);
mAnimator.setRepeatCount(Animation.INFINITE);
mAnimator.setRepeatMode(Animation.RESTART);
mAnimator.start();
如果在Activity中播放此类动画并且在onDestroy()方法中没有停止该动画,那么动画会一直循环下去,尽管在界面上已经无法看不到动画了,但这个时候Activity的View会被动画持有,而View又持有Activity,最终Activity无法释放。由于动画是无限循环的,会泄露当前的Activity。
【解决方法】适当的时机(如onDestroy()方法中)调用Animator.cancel()方法。
if (mAnimator != null) {
mAnimator.cancel();
}
4. 非静态内部类持有外部类实例
public class MainActivity extends Activity
{
static Demo sInstance = null;
@Override
public void onCreate(BundlesavedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (sInstance == null)
{
sInstance= new Demo();
}
}
class Demo
{
voiddoSomething()
{
System.out.print("doth.");
}
}
}
第一个MainActivity activity1实例创建时,sInstance会获得并一直持有activity1的引用。
【解决方法】WeakReference引用外部实例。
public class OutterClass
{
......
......
static class InnerClass
{
private final WeakReference<OutterClass> mOutterClassInstance;
......
......
final OutterClass outer = mOutterClassInstance.get();
}
}
5. 注册某个对象后未反注册
【解决方法】注册广播接收器、注册观察者等,在退出时需要进行反注册。
6. 资源对象没关闭造成的内存泄露
资源性对象比如(Cursor,File文件等)往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们,以便它们的缓冲及时回收内存。它们的缓冲不仅存在于Java虚拟机内,还存在于Java虚拟机外。如果我们仅仅是把它的引用设置为null,而不关闭它们,往往会造成内存泄露。
【解决方法】对于资源性对象在不使用的时候,应该立即调用它的close()函数,将其关闭掉,然后再置为null.在我们的程序退出时一定要确保我们的资源性对象已经关闭。
7. Handler类、HandlerThread类使用不当
Handler类,Handler通过发送Message与其他线程交互,Message发出之后是存储在目标线程的MessageQueue中的,而有时候Message也不是马上就被处理的,可能会驻留比较久的时间。
在Message类中存在一个成员变量target,它强引用了handler实例,如果Message在Queue中一直存在,就会导致handler实例无法被回收,如果handler对应的类是非静态内部类,则会导致外部类实例(Activity或者Service)不会被回收,这就造成了外部类实例的泄露。
HandlerThread实现的run方法是一个无限循环,它不会自己结束,线程的生命周期超过了activity生命周期,当横竖屏切换,HandlerThread线程的数量会随着activity重建次数的增加而增加。
public classMainActivity extends Activity
{
@Override
public void onCreate(BundlesavedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Thread mThread = newHandlerThread("demo", Process.THREAD_PRIORITY_BACKGROUND);
mThread.start();
MyHandler mHandler = new MyHandler(mThread.getLooper());
....
}
@Override
public void onDestroy()
{
super.onDestroy();
}
}
【解决方法】应该在onDestroy时将线程停止掉:mThread.getLooper().quit()。对于不是HandlerThread的线程,也应该确保activity消耗后,线程已经终止,可以这样做:在onDestroy时调用mThread.join()。
8. 线程、AsyncTask类造成内存泄露
Runnable和AsyncTask都可以是一个匿名内部类,它们对当前的Activity都有一个隐式引用。如果Activity在销毁前,任务未完成,将导致Activity的内存资源无法回收。
【解决方法】同样是使用静态内部类来解决。
9. 构造Adapter时,没有使用缓存的convertView
public View getView(intposition, View convertView, ViewGroup parent)
getView()为ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的listitem。这个构造过程就是由getView()方法完成的,getView()的第二个形参convertView就是被缓存起来的listitem的view对象(初始化时缓存中没有view对象则convertView是null)。
public View getView(intposition, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null){
view = convertView;
populate(view, getItem(position));
} else {
view = new Xxx(...);
}
return view;
}
10. Bitmap使用不当
(1) 及时的销毁
系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过Java堆的限制。因此,在用完Bitmap时,要及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉。
(2) 设置一定的采样率
BitmapFactory.Options options = newBitmapFactory.Options();
options.inSampleSize = 2;
(3) 运用软引用(SoftRefrence)
我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。
SoftReference<Bitmap> bitmap_ref = new SoftReference<Bitmap>(BitmapFactory.decodeStream(inputstream));
....
if (bitmap_ref.get() != null) {
bitmap_ref.get().recycle();
}
11. 及时消除集合类引用
通常会把一些对象的引用加入到了集合中,当我们不需要该对象时,如果没有把它的引用从集合中清理掉,这样这个集合就会越来越大。如果这个集合是static的话,可能更严重。长时间地使用手机才能出现OOM。
【解决方法】集合类对普通对象的引用,在不需要时要及时从集合类中消除引用。
参考:
http://blog.csdn.net/gemmem/article/details/13017999
http://www.cnblogs.com/yejiurui/archive/2013/02/23/2923418.html
http://www.kancloud.cn/digest/itfootballprefermanc/100913