Android开发中常见内存泄漏问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/cugwuhan2014/article/details/78043430

##一、内存泄漏原因
当一个对象不再使用时,本该被回收,而另一个正在使用的对象持有它的引用导致不能被回收,就产生了内存泄漏。

##二、内存泄漏的影响
Android系统为每个应用程序分配的内存有限,当应用中内存泄漏较多时,轻则造成可用空间不足,频繁发生gc,表现为应用运行卡顿;重则导致内存溢出应用crash。

##三、常见内存泄漏及解决办法
###3.1 单例造成内存泄漏

public class AppManager{
	private static volatile AppManager instance = null;
	private Context mContext;

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

	public static AppManager getInstance(Context context){
		if(null == instance){
			synchronized(AppManager.class){
				if(null == instance){
				    instance = new AppManager(context);
				}
	        }
	     }
	     return instance;
	   }
}

ps:单例模式为何加双重检查?申明单例的静态引用为何加volatile关键字?

由于单例的生命周期和Application一样长,当Context所对应的Activity退出时,由于单例持有该Activity的引用,造成Activity退出时内存不能得到回收。
修正方法:
this. mContext = context;改为
this. mContext = context.getApplicationContext();
无论传入什么Context,最终都将使用Application的Context,防止了内存泄漏。

###3.2 非静态内部类创建静态实例造成内存泄漏

public class OuterClassActivity extern Activity{
	private static Inner mInner = null;

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

		if(null == mInner){
			mInner = new Inner();//创建静态实例
		}
	}
	
	private class Inner{//非静态内部类
		//TODO
	}
}

由于非静态内部类会持有外部类的引用,当在内部类创建非静态内部类的静态实例后,导致该静态实例会持续持有外部类的应用,造成内存资源不能正常回收。
修正方法:将内部类设为静态内部类,静态内部类不会持有外部类实例的引用,当需要用到外部类的方法或属性时,使用外部类实例的弱引用。例如内部类可以这么写

private static class Inner{
	private WeakReference<OuterClassActivity> mActivity;
	
	public Inner(OuterClassActivity activity){
		mActivity = new WeakReference<OuterClassActivity>( activity); 
	}
}

###3.3 Handler造成的内存泄漏

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new Handler() {
    	
	    @Override
	    public void handleMessage(Message msg) {
	         //TODO
	    }
    };
}

由于mHandler是Handler的非静态匿名内部类的实例,它会持有外部类Activity的引用。消息队列是在一个Looper线程中不断轮询处理消息,如果当这个Activity退出时消息还未处理完毕,消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,导致Activity退出时无法释放内存,引发内存泄漏。
修正方法:创建静态的Handler内部类,然后对Handler持有的activity对象使用弱引用,这样可以避免Activity内存泄漏,不过Looper线程的消息队列还是可能会有待处理的消息,所以在Activity 的onDestory方法中应该移除消息队列中的消息。

public class MainActivity extends AppCompatActivity {
    private Handler mHandler = new MyHandler (this);

    private  static class MyHandler extern Handler{
	    private WeakReference<Context> softRefContext;
	    public MyHandler(Context context) {
	    	softRefContext = new WeakReference<Context>(context);
	    }
	    
	   @Override
	   public void handleMessage(Message msg) {
	         //TODO
	   }
   };


	@Override
	protected void onDestroy() {
		if (mHandler != null){
			 mHandler.removeCallbacksAndMessages(null);
			 mHandler = null;
		}
		super.onDestroy();
	}
}

###3.4 线程造成的内存泄漏
为避免阻塞主线程,在Activity中创建线程执行耗时操作是比较常见的,如

new Thread(new Runnable() {
    @Override
    public void run() {
        Log.i(TAG, "线程创建成功,正在执行线程程序");
        SystemClock.sleep(30*1000);
        //TODO
    }
}).start();

上面的Runnable是一个匿名内部类,因此它对当前Activity有一个隐式引用。如果Activity在销毁之前,任务还未完成,将导致Activity的内存资源无法回收,造成内存泄漏。
正确做法:使用静态内部类,

static class MyRunnable implements Runnable{
    @Override
    public void run() {
        Log.i(TAG, "线程创建成功,正在执行线程程序");
        SystemClock.sleep(30*1000);
    }
}
//--------------------------------------------------------------------------
new Thread(new MyRunnable()).start();

在Activity销毁时应该取消相应的任务,避免在后台执行浪费资源。

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

##总结
非静态内部类、匿名内部类会隐式持有外部类对象,需要注意其生命周期,建议使用静态内部类配合弱引用访问外部类,避免一不小心造成内存泄漏。
并不是所有内部类只能使用静态内部类,当该类的生命周期不可控时,我们需要采用静态内部类。
内部类存在的意义:接口只能解决部分多重继承问题,而内部类可以使多重继承更加完善,当需要继承抽象类或者具体类时,只能使用内部类才能实现多重继承。

内存泄漏主要分为以下几种类型:
1.静态变量(包括但不限于单例)引起的内存泄漏。注意静态变量持有对象的生命周期。
2.非静态内部类引起的内存泄漏。静态内部类,弱引用访问。
3.匿名内部类引起的内存泄漏。静态内部类,弱引用访问。
4.资源未关闭引起的内存泄漏。退出前关闭资源。

猜你喜欢

转载自blog.csdn.net/cugwuhan2014/article/details/78043430