常见的Android内存泄漏问题以及解决办法

常见的Android内存泄漏问题以及解决办法

什么是内存泄漏以及其危害

编写代码的时候由于错误或者疏忽,致使一部份内存空间不能被垃圾回收机制回收,形成这部分的内存空间浪费,不再能被程序使用到,这就叫作内存泄漏。这部份内存并非消失不见了,而是这部份内存当中存储的数据已经再也不使用,可是又占用着位置,致使物理内存空间得不到释放。java

内存泄漏会致使程序可以使用的内存减小,从而形成OOM(out of memory)等不利因素,形成程序崩溃。android

常见内存泄漏以及解决办法

单例模式形成的内存泄漏

通常状况下,单例模式的生命周期是跟应用的生命周期同样长的,因此当单例模式中的对象持有一个须要释放的对象时,这就会致使这个须要释放的对象得不到释放,从而形成内存泄漏。好比以下例子:web

class AppFactory{
    private static AppFactory sAppFactory;
    private Context mContext;

    private AppFactory(Context context){
        this.mContext = context;
    }

    public static AppFactory getInstance(Context context){
        if(sAppFactory == null){
            sAppFactory = new AppFactory(context);
        }
        return sAppFactory;
    }
}

当context传入的是一个activity的时候,则会形成内存泄漏,由于当activity关闭的时候,由于单例对象还持有activity的引用,致使这个activity对象不可以被释放回收,从而形成浪费。缓存

解决办法: app

一、把传入的activity对象改为applicationContextide

二、把上述代码改为以下:svg

class AppFactory{
    private static AppFactory sAppFactory;
    private Context mContext;

    private AppFactory(Context context){
        this.mContext = context.getApplicationContext();
    }

    public static AppFactory getInstance(Context context){
        if(sAppFactory == null){
            sAppFactory = new AppFactory(context);
        }
        return sAppFactory;
    }
}

无论传入的是什么context,最终都变成了applicationContext,这个生命周期就是应用程序的生命周期,从而避免了内存泄漏。性能

注: 上面标注了一个“通常状况下”,也就是正常状况下,而非正常状况就是内存不够的时候,系统会把静态对象直接置为null,因此这就不存在持有context的引用了。同时,当要持有持久性数据时,不要用static来保存这个数据。this

非静态内部类形成的内存泄漏

不少时候,处于各类缘由,咱们都会在一个类里面建立另外的类,也就是内部类,无论匿名的仍是非匿名的,都会建立一个或者多个。因为内部类会默认持有外部类的一个引用,因此当使用内部类的时候,若是内部类对象没有及时释放外部类的引用,则会形成内存泄漏。spa

内部类形成的内存泄漏有如下几种状况:

一、非静态内部类的静态对象

代码以下:

class MainActivity extends Activity{

    private static Test sTest;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if(sTest == null){
            sTest = new Test();
        }
    }

    class Test{
    }

}

这里为了不重复建立Test对象,因此使用了static关键字。可是由于静态对象的生命周期跟应用程序的生命周期是同样长的,因此当关闭这个activity的时候会形成内存泄漏。

解决办法: 将该内部类拿出来封装成一个单例,若是使用到了Context,则使用applicationContext。

二、Handler形成的内存泄漏
class MainActivity extends Activity{

    private Handler mHandler = new Handler(){
        @Override
        public void handleMessage(Message msg) {
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

由于Handler是非静态内部类,因此会持有外部类的引用。可是有人会问:这个Handler对象不是静态的,这怎么也会形成内存泄漏呢?Handler里面是有个消息队列的,消息队列里可以存储不少消息,而handler处理消息是要时间的,好比在还有消息须要处理的时候,把activity关闭了,由于Handler还在处理消息,确定不可能释放掉外部类的引用的,即activity的引用,因此这个就形成了内存泄漏。

解决办法: 首先把handler类定义成静态的,而后显示的持有外部类的引用,可是这个持有不能是强引用,而是使用弱引用,这样当回收时就能够释放外部类的引用了,代码以下:

class MainActivity extends Activity{

    private MyHandler myHandler = new MyHandler(this);

    private static class MyHandler extends Handler {

        private WeakReference<Context> reference;
        public MyHandler(Context context) {
            reference = new WeakReference<>(context);
        }

        @Override
        public void handleMessage(Message msg) {
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

这样一改以后就能够避免activity的内存泄漏了,可是当还有消息队列里面还有消息或者正在处理最后一个消息时,虽然activity不会内存泄漏了,可是这些剩余的消息或者正在处理的消息会形成一些内存泄漏,因此最好的作法是在onstop方法或者ondestory方法里把这些消息移除掉,代码以下:

class MainActivity extends Activity{

    private MyHandler myHandler = new MyHandler(this);

    private static class MyHandler extends Handler {

        private WeakReference<Activity> reference;
        public MyHandler(Activity activity) {
            reference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            Activity activity = reference.get();
            if(activity == null){
                return;
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    protected void onDestroy() {
       super.onDestroy();
        myHandler.removeCallbacksAndMessages(null);
    }
}

使用mHandler.removeCallbacksAndMessages(null);是移除消息队列中全部消息和全部的Runnable。固然也可使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。

三、activity中线程内部类形成内存泄漏

由于内部类会持有外部类的引用,而线程又是用来处理耗时操做的,因此当线程还在处理耗时操做时就把activity关闭了,这就会形成内存泄漏。

解决办法: 显示的持有外部类的弱引用,这个跟上面的是同样的,只是不须要移除消息队列里的消息。

资源未关闭形成的内存泄漏

对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream等资源的使用,应该在Activity销毁时及时关闭或者注销,不然这些资源将不会被回收,形成内存泄漏。

Bitmap使用后未释放内存形成的内存泄漏

若是Bitmap形成的内存泄漏,很大的可能会直接致使程序崩溃。因此当一个Bitmap对象不使用时,必定要调用recycle()释放内存。若是Bitmap对象没有释放,那么很容易就会出现OOM,从而致使应用程序崩溃。

集合中的对象未清理掉

咱们常常把一些对象的引用加入到集合中,可是当这些对象的引用不用的时候,咱们并无把这些对象清理掉,这样会形成内存泄漏,致使集合的内存愈来愈大。若是这个集合是static的,那么危害更大。

ListView以及其余使用BaseAdapter的控件形成的内存泄漏

当给ListView设置Adapter的时候会继承BaseAdapter,其中有个方法是public View getView(int position, View convertView, ViewGroup parent),其中第二个参数会被缓存起来,若是在这个方法里面始终建立一个View来返回,那么这个缓存的View就浪费掉了,使用不到,从而形成内存泄漏。并且由于要从新建立一个View,这里会形成性能的消耗,即浪费资源,也浪费时间。

解决办法:判断convertView是否为空,若是为空,则建立这个View,若是不为空,则直接使用convertView,即不会形成内存泄漏,也能够提升性能。