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

来源:互联网 发布:数据预处理的基本原理 编辑:程序博客网 时间:2024/05/21 12:09

一、内存泄漏原因

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

二、内存泄漏的影响

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.资源未关闭引起的内存泄漏。退出前关闭资源。

阅读全文
0 0