Android中常见的内存泄漏&解决方法

内存泄漏也称作“存储渗漏”,用动态存储分配函数动态开辟的空间,在使用完毕后未释放,结果导致一直占据该内存单元。直到程序结束。(其实说白了就是该内存空间使用完毕之后未回收)即所谓内存泄漏。经常表现为:OutOfMemory等等。
 

下面列出常见的5种内存泄漏:

 

1.万恶的static

static是Java中的一个关键字,当用它来修饰成员变量时,那么该变量就属于该类,而不是该类的实例。所以用static修饰的变量,它的生命周期是很长的,如果用它来引用一些资源耗费过多的实例(Context的情况最多),这时就要谨慎对待了。

public class ClassName {
private static Context mContext; //省略
}

以上的代码是很危险的,如果将Activity赋值到么mContext的话。那么即使该Activity已经onDestroy,但是由于仍有对象保存它的引用,因此该Activity依然不会被释放.

如何才能有效的避免这种引用的发生呢?

  1. 第一,应该尽量避免static成员变量引用资源耗费过多的实例,比如Context。
  2. 第二、Context尽量使用Application Context,因为Application的Context的生命周期比较长,引用它不会出现内存泄露的问题。
  3. 第三、使用WeakReference代替强引用。比如可以使用WeakReference<Context> mContextRef;

2.线程惹的祸

线程也是造成内存泄露的一个重要的源头。线程产生内存泄露的主要原因在于线程生命周期的不可控。我们来考虑下面一段代码。

public class MyActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
new MyThread().start();
}

private class MyThread extends Thread {
    @Override
    public void run() {
        super.run();

        //do somthing 
    }
}

}
这段代码很平常也很简单,是我们经常使用的形式。我们思考一个问题:

假设MyThread的run函数是一个很费时的操作,当我们开启该线程后,将设备的 横屏变为了竖屏,一般情况下当屏幕转换时会重新创建Activity,按照我们的想法,老的Activity应该会被销毁才对,然而事实上并非如此。

由于我们的线程是Activity的内部类,所以MyThread中保存了Activity的一个引用,当MyThread的run函数没有结束 时,MyThread是不会被销毁的,因此它所引用的老的Activity也不会被销毁,因此就出现了内存泄露的问题。

这种线程导致的内存泄露问题应该如何解决呢?

  • 第一、将线程的内部类,改为静态内部类。
  • 第二、在线程内部采用弱引用保存Context引用。
    另外,我们都知道Hanlder是线程与Activity通信的桥梁,我们在开发好多应用中会用到线程,有些人处理不当,会导致当程序结束时,线程并没有 被销毁,而是一直在后台运行着,当我们重新启动应用时,又会重新启动一个线程,周而复始,你启动应用次数越多,开启的线程数就越多,你的机器就会变得越慢。
    package com.tutor.thread;

import android.app.Activity;

import android.os.Bundle;
import android.os.Handler;

import android.util.Log;

public class ThreadDemo extends Activity {
private static final String TAG = “ThreadDemo”;
private int count = 0;
private Handler mHandler = new Handler();
private Runnable mRunnable = new Runnable() {
public void run() {
//为了方便 查看,我们用Log打印出来
Log.e(TAG, Thread.currentThread().getName() + “ “ + count);
count++;
setTitle(“” + count);
//每2秒执行一次
mHandler.postDelayed(mRunnable, 2000);
}
};

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);
    //通过Handler启动线程 
    mHandler.post(mRunnable);
}

}

 

所以我们在应用退出时,要将线程销毁,我们只要在Activity中的,onDestory()方法处理一下就OK了,如下代码所示:

@Override
protected void onDestroy() {
mHandler.removeCallbacks(mRunnable);
super.onDestroy();
}

 

3.超级大胖子Bitmap

可以说出现OutOfMemory问题的绝大多数人,都是因为Bitmap的问题。因为Bitmap占用的内存实在是太多了,它是一个“超级大胖子”,特别是分辨率大的图片,如果要显示多张那问题就更显著了。

如何解决Bitmap带给我们的内存问题?

  • 第一、及时的销毁。
    虽然,系统能够确认Bitmap分配的内存最终会被销毁,但是由于它占用的内存过多,所以很可能会超过java堆的限制。因此,在用完Bitmap时,要 及时的recycle掉。recycle并不能确定立即就会将Bitmap释放掉,但是会给虚拟机一个暗示:“该图片可以释放了”。

  • 第二、设置一定的采样率。
    有时候,我们要显示的区域很小,没有必要将整个图片都加载出来,而只需要记载一个缩小过的图片,这时候可以设置一定的采样率,那么就可以大大减小占用的内存。如下面的代码:
    private ImageView preview;
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2;//图片宽高都为原来的二分之一,即图片为原来的四分之一
    Bitmap bitmap = BitmapFactory.decodeStream(cr.openInputStream(uri), null, options); preview.setImageBitmap(bitmap);

  • 第三、巧妙的运用软引用(SoftRefrence)
    有些时候,我们使用Bitmap后没有保留对它的引用,因此就无法调用Recycle函数。这时候巧妙的运用软引用,可以使Bitmap在内存快不足时得到有效的释放。

4.行踪诡异的Cursor

Cursor是Android查询数据后得到的一个管理数据集合的类,正常情况下,如果查询得到的数据量较小时不会有内存问题,而且虚拟机能够保证Cusor最终会被释放掉。
然而如果Cursor的数据量特表大,特别是如果里面有Blob信息时,应该保证Cursor占用的内存被及时的释放掉,而不是等待GC来处理。并且Android明显是倾向于编程者手动的将Cursor close掉

#
5.构造Adapter时,没有使用缓存的 convertView

描述:
以构造ListView的BaseAdapter为例,在BaseAdapter中提高了方法:

public View getView(int position, View convertView, ViewGroup parent)

来向ListView提供每一个item所需要的view对象。初始时ListView会从BaseAdapter中根据当前的屏幕布局实例化一定数量的 view对象,同时ListView会将这些view对象缓存起来。当向上滚动ListView时,原先位于最上面的list item的view对象会被回收,然后被用来构造新出现的最下面的list item。这个构造过程就是由getView()方法完成的,getView()的第二个形参 View convertView就是被缓存起来的list item的view对象(初始化时缓存中没有view对象则convertView是null)。
由此可以看出,如果我们不去使用convertView,而是每次都在getView()中重新实例化一个View对象的话,即浪费资源也浪费时间,也会使得内存占用越来越大。ListView回收list item的view对象的过程可以查看:
android.widget.AbsListView.java –> void addScrapView(View scrap) 方法。
示例代码:

public View getView(int position, View convertView, ViewGroup parent) {
View view = new Xxx(…);
//… …
return view;
}

修正示例代码:

public View getView(int position, View convertView, ViewGroup parent) {
View view = null;
if (convertView != null) {
view = convertView;
populate(view, getItem(position));
//…
} else {
view = new Xxx(…);
//…
}
return view;
}

 

小结:
static:引用了大对象如context
线程:切屏时Activity因为线程引用而没有如期被销毁;handler有关,Activity意外终止但线程还在
Bitmap:要及时recycle,降低采样率
Cursor:要及时关闭
Adapter:没有使用缓存的convertView