关于Android的内存泄漏
知识补充:
<1.静态内部类和外部类生命周期区别:
生命周期:静态内部类随着外部类的加载而加载,而不是随着外部类对象的产生而产生。
外部类实例 与静态内部类实例是没有关系的。
外部内部类实例对应着不同的非静态内部类实例。
1.什么是内存泄漏
程序在运行中,一些不需要的资源要被回收,释放内存,但是在回收的时候,该资源被其他地方使用或持有着,导致内存无法释放,造成内存泄漏。
2.内存泄漏可能导致的后果
Android 程序在运行的时候,Android 系统会分配给单个程序一定的运行空间,在运行过程中会产生很多不用的资源需要回收,来保持足够的内存支持程序的运行,内存泄漏导致需要回收的资源无法释放内存,这使得软件长时间运行,无法被回收的空间越来越多,挤压软件的运行,导致软件卡顿,甚至是 out of memory ,用户体验很差,所以,我们要尽量避免出现内存泄漏。
3.发现内存泄漏
检测内存泄漏的工具很多,这里介绍其中一个,LeakCanary,使用很简单,具体的使用方法请参考这篇文章http://www.jcodecraeer.com/a/anzhuokaifa/androidkaifa/2015/0509/2854.html 对LeakCanary 介绍的很详细了。
4.那些写法容易造成内存泄漏
新手写代码的时候,不经意间就会造成内存泄漏,这里分析了一下内存泄漏最容易出现的几种情况。
1.单利造成的内存泄漏
我们经常使用单例的写法,但是单例的生命进程是跟随整个app进程的,所以,如果这时单例持有某个资源,会导致资源在单例还存活时释放不掉。
举个栗子:
public class AppManager {
private static AppManager instance;
private Context context;
private AppManager(Context context) {
this.context = context;
}
public static AppManager getInstance(Context context) {
if (instance != null) {
instance = new AppManager(context);
}
return instance;
}
}
分析:这里问题出现在context上面,因为单例和应用的application 生命周期一样长,所以持有context的时间也很长,在传入的context 为application 的context的时候是不会有问题的,但是如果传入的是activity的context,当activity退出的时候因为该单例持有着这个activity的context 导致 及时activity退出了,也不会释放内存,导致内存泄漏。
修复:在这里 我们修改 this.context = context;让context为application的context,修改为:this.context=context.getApplicationContext();
2.非静态内部类创建静态实力造成的内存泄漏
非静态内部类是默认持有外部类对象的,如果在创建非静态内部类对象的时候定义为了静态也就是static 使得static定义让其生命周期跟应用一样长,即使activity此时外部类关闭了内部类也不能被释放,导致内存泄漏。
举个栗子:
修复:把private static Testthetest; 把static去掉。
3.线程造成的内存泄漏
多线程,我们经常用到,但是使用不当就会造成内存泄漏。
举个栗子:
//——————test1
new AsyncTask() {
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
}.execute();
//——————test2
new Thread(new Runnable() {
@Override
public void run() {
SystemClock.sleep(10000);
}
}).start();
分析:上面的异步任务和Runnable都是一个匿名内部类,因此它们对当前Activity都有一个隐式引用。如果Activity在销毁之前,任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。
修复:
static class MyAsyncTask extends AsyncTask {
private WeakReference weakReference;
public MyAsyncTask(Context context) {
weakReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
SystemClock.sleep(10000);
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
MainActivity activity = (MainActivity) weakReference.get();
if (activity != null) {
//...
}
}
}
static class MyRunnable implements Runnable{
@Override
public void run() {
SystemClock.sleep(10000);
}
}
new Thread(new MyRunnable()).start();
new MyAsyncTask(this).execute();
使用静态内部类就避免了activity的内存资源泄露,当然要在activity销毁时取消相应的任务,避免任务在后台继续执行,让费资源。
4.资源未关闭造成的内存泄漏
对于使用了BraodcastReceiver,ContentObserver,File,Cursor,Stream,Bitmap等资源的使用,应该在Activity销毁时及时关闭或者注销,否则这些资源将不会被回收,造成内存泄漏。
5.Handler造成的内存泄漏
handler我们在处理异步问题时经常用到,不仔细看没问题,仔细看就会发现其中的猫腻。
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
//...
}
};
分析:我们经常这样写,但是 这种创建handler的方式会造成内存泄漏,我们在这样创建的handler输入非静态内部类,非静态内部类默认持有外部类的引用,这里造成内存泄漏的原因是,handler发送消息之后,消息会进入消息队列Looper中去排队,当消息还未处理 而activity这时已经退出时,消息队列中的Message又持有着mHandler的实例,而mHandler又持有着Activity引用,导致内存泄漏。
修复:在onDestory方法中调用mHandler.removeCallbacksAndMessages(null);是移除消息队列中所有消息和所有的Runnable。当然也可以使用mHandler.removeCallbacks();或mHandler.removeMessages();来移除指定的Runnable和Message。
再举个栗子:
我们在主线程写延时操作的时候其中的一种写法View.postDelayed(new Runnable() {
@Override
public void run() {
doSomeThing();
}
}, 1000);
在主线程延迟1秒之后执行doSomeThing();
这里如果activity在不到一秒的时候关闭了,同样会造成内存泄漏,这个方法造成内存泄漏的方式几乎原因是一样的。
结论总结:
这里简单的分析了一下内存泄漏的常见几种,还有修复方案。毕竟本人技术知识有限,难免会有不准确的理解,请大神们勿喷。因为内存泄漏很常见,在新手眼中又不好解决,希望对看到文章的亲们有所帮助。