内存泄漏缘由及解决方案

什么是内存泄漏?

内存泄漏是当程序再也不使用到的内存时,释放内存失败而产生了无用的内存消耗。内存泄漏并非指物理上的内存消失,这里的内存泄漏是值由程序分配的内存可是因为程序逻辑错误而致使程序失去了对该内存的控制,使得内存浪费。java

怎样会致使内存泄漏?

  • 资源对象没关闭形成的内存泄漏,如查询数据库后没有关闭游标cursor
  • 构造Adapter时,没有使用 convertView 重用
  • Bitmap对象不在使用时调用recycle()释放内存
  • 对象被生命周期长的对象引用,如activity被静态集合引用致使activity不能释放

在接下来的篇幅里,咱们重点讲有关Activity常见的内存泄漏。数据库

内存泄漏1:静态Activities(static Activities)

代码以下:
MainActivity.javaapp

public class MainActivity extends AppCompatActivity {
    private static MainActivity activity;
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticActivity();
                nextActivity();
            }
        });
    }
    void setStaticActivity() {
        activity = this;
    }

    void nextActivity(){
        startActivity(new Intent(this,RegisterActivity.class));
        SystemClock.sleep(1000);
        finish();
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        //使用LeakCanary观察是否有内存泄漏
        MyApplication.getRefWatcher().watch(this);
    }
}

LeakCanary检测出的内存泄漏:ide

这里写图片描述

为何?
在上面代码中,咱们声明了一个静态的Activity变量而且在TextView的OnClick事件里引用了当前正在运行的Activity实例,因此 若是在activity的生命周期结束以前没有清除这个引用,则会引发内存泄漏。由于声明的activity是静态的,会常驻内存,若是该对象不清除,则 垃圾回收器没法回收变量。oop

怎么解决?
在onDestory方法中将静态变量activity置空,这样垃圾回收器就能够将静态变量回收。post

@Override
    protected void onDestroy() {
        super.onDestroy();
        activity = null;
        //使用LeakCanary观察是否有内存泄漏
        MyApplication.getRefWatcher().watch(this);
    }

内存泄漏2:静态View

代码以下:
MainActivity.javathis

...
    private static View view;
    TextView saButton;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        saButton = (TextView) findViewById(R.id.text);
        saButton.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View v) {
                setStaticView();
                nextActivity();
            }
        });
    }
    void setStaticView() {
        view = findViewById(R.id.sv_view);
    }
    ...

LeakCanary检测到的内存泄漏spa

这里写图片描述

为何?
上面代码看似没有问题,在Activity里声明一个静态变量view,而后初始化,当Activity生命周期结束了内存也释放了,可是 LeakCanary却显示出现了内存泄漏,为何?问题出在这里,View一旦被加载到界面中将会持有一个Context对象的引用,在这个例子中,这 个context对象是咱们的Activity,声明一个静态变量引用这个View,也就引用了activity,因此当activity生命周期结束 了,静态View没有清除掉,还持有activity的引用,所以内存泄漏了。线程

怎么解决?
在onDestroy方法里将静态变量置空。code

@Override
protected void onDestroy() {
    super.onDestroy();
    view = null;
    MyApplication.getRefWatcher().watch(this);
}

内存泄漏3:内部类

代码以下:
MainActivity.java

private static Object inner;
void createInnerClass() {
    class InnerClass {
    }
    inner = new InnerClass();
}

View icButton = findViewById(R.id.ic_button);
icButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createInnerClass();
        nextActivity();
    }
});

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为何?
非静态内部类会持有外部类的引用,在上面代码中内部类持有Activity的引用,所以inner会一直持有Activity,若是Activity生命周期结束没有清除这个引用,这样就发生了内存泄漏。

怎么解决?
由于非静态内部类隐式持有外部类的强引用,因此咱们将内部类声明成静态的就能够了。

void createInnerClass() {
    static class InnerClass {
    }
    inner = new InnerClass();
}

内存泄漏4:匿名类

void startAsyncTask() {
    new AsyncTask<Void, Void, Void>() {
        @Override protected Void doInBackground(Void... params) {
            while(true);
        }
    }.execute();
}

super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
View aicButton = findViewById(R.id.at_button);
aicButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        startAsyncTask();
        nextActivity();
    }
});

使用LeakCanary检测到的内存泄漏:

这里写图片描述

为何?
上面代码在activity中建立了一个匿名类AsyncTask,匿名类和非静态内部类相同,会持有外部类对象,这里也就是activity,所以若是 你在Activity里声明且实例化一个匿名的AsyncTask对象,则可能会发生内存泄漏,若是这个线程在Activity销毁后还一直在后台执行, 那这个线程会继续持有这个Activity的引用从而不会被GC回收,直到线程执行完成。

怎么解决?
自定义静态AsyncTask类,而且让AsyncTask的周期和Activity周期保持一致,也就是在Activity生命周期结束时要将AsyncTask cancel掉。

内存泄漏5:Handler

代码以下:
MainActivity.java

...
void createHandler() {
    new Handler() {
        @Override public void handleMessage(Message message) {
            super.handleMessage(message);
        }
    }.postDelayed(new Runnable() {
        @Override public void run() {
            while(true);
        }
    }, 1000);
}

...
View hButton = findViewById(R.id.h_button);
hButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        createHandler();
        nextActivity();
    }
});
...

为何?
当Android Application启动之后,framework会首先帮助咱们完成UI线程的消息循环,也就是在UI线程中,Loop、MessageQueue、 Message等等这些实例已经由framework帮咱们实现了。全部的Application主要事件,好比Activity的生命周期方法、 Button的点击事件都包含在这个Message里面,这些Message都会加入到MessageQueue中去,因此,UI线程的消息循环贯穿于整 个Application生命周期,因此当你在UI线程中生成Handler的实例,就会持有Loop以及MessageQueue的引用。而且在 Java中非静态内部类和匿名内持有外部类的引用,而静态内部类则不会持有外部类的引用。

怎么解决?
能够由上面的结论看出,产生泄漏的根源在于匿名类持有Activity的引用,所以能够自定义Handler和Runnable类并声明成静态的内部类,来解除和Activity的引用。

内存泄漏6:Thread

代码以下:
MainActivity.java

void spawnThread() {
    new Thread() {
        @Override public void run() {
            while(true);
        }
    }.start();
}

View tButton = findViewById(R.id.t_button);
tButton.setOnClickListener(new View.OnClickListener() {
  @Override public void onClick(View v) {
      spawnThread();
      nextActivity();
  }
});

为何?
同AsyncTask同样,这里就不过多赘述。

怎么解决?
那咱们自定义Thread并声明成static这样能够吗?其实这样的作法并不推荐,由于Thread位于GC根部,DVM会和全部的活动线程保持 hard references关系,因此运行中的Thread毫不会被GC无故回收了,因此正确的解决办法是在自定义静态内部类的基础上给线程加上取消机制,所以 咱们能够在Activity的onDestroy方法中将thread关闭掉。

内存泄漏7:Timer Tasks

代码以下:
MainActivity.java

void scheduleTimer() {
    new Timer().schedule(new TimerTask() {
        @Override
        public void run() {
            while(true);
        }
    },1000);
}

View ttButton = findViewById(R.id.tt_button);
ttButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        scheduleTimer();
        nextActivity();
    }

为何?
这里内存泄漏在于Timer和TimerTask没有进行Cancel,从而致使Timer和TimerTask一直引用外部类Activity。

怎么解决?
在适当的时机进行Cancel。

内存泄漏8:Sensor Manager

代码以下:
MainActivity.java

void registerListener() {
       SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE);
       Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_ALL);
       sensorManager.registerListener(this, sensor, SensorManager.SENSOR_DELAY_FASTEST);
}

View smButton = findViewById(R.id.sm_button);
smButton.setOnClickListener(new View.OnClickListener() {
    @Override public void onClick(View v) {
        registerListener();
        nextActivity();
    }
});

为何?
经过Context调用getSystemService获取系统服务,这些服务运行在他们本身的进程执行一系列后台工做或者提供和硬件交互的接口,若是 Context对象须要在一个Service内部事件发生时随时收到通知,则须要把本身做为一个监听器注册进去,这样服务就会持有一个Activity, 若是开发者忘记了在Activity被销毁前注销这个监听器,这样就致使内存泄漏。

怎么解决?
在onDestroy方法里注销监听器。

总结

在开发中,内存泄漏最坏的状况是app耗尽内存致使崩溃,可是每每真实状况不是这样的,相反它只会耗尽大量内存但不至于闪退,可分配的内存少 了,GC便会更多的工做释放内存,GC是很是耗时的操做,所以会使得页面卡顿。咱们在开发中必定要注意当在Activity里实例化一个对象时看看是否有 潜在的内存泄漏,必定要常常对内存泄漏进行检测。

相关文章
相关标签/搜索