常见内存泄漏案例及解决方案

内存泄漏
产生的原因: 一个长生命周期的对象持有一个短生命周期对象的引用,通俗点讲就是该回收的对象,因为引用问题没有被回收,最终会产生 OOM。

内存泄漏的常见场景
1.非静态内部类的静态实例持有外部类的实例的引用。
代码举例:

public class InnerClassLeakActivity extends AppCompatActivity {
    private static Leak sLeak;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak_common);
        sLeak = new Leak();
        sLeak.test();

    }

    private class Leak {
        private void test(){
            LogUtil.i("test method execute");
        }
    }
    
    @Override
    protected void onDestroy() {
        super.onDestroy();
        RefWatcher refWatcher = MyApplication.getRefWatcher(this);
        refWatcher.watch(this);
    }
}

由于非静态内部类创建的静态实例的生命周期是和应用的生命周期一样长的,这个静态实例会一直持有外部类InnerClassLeakActivity的引用,导致即使InnerClassLeakActivity销毁,它仍然不会被GC回收,这样就导致内存泄漏了。解决方案:将非静态内部类声明为静态内部类。

2.多线程相关的匿名内部类或非静态内部类
匿名内部类也会持有外部类的实例的引用。多线程相关的类有AsyncTask,Thread,Runnable接口等,它们都是
匿名内部类或非静态内部类,如果做耗时操作,就有可能发生内存泄漏。
代码举例:

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


或者

class MyAsyncTask extends AsyncTask<Void,Void,Void>{
	@override
	protected void doInBackground(Void... params){
		while(true);
	}

}

new MyAsyncTask().execute();

上面两种方式,不管是匿名内部类还是非静态内部类,它们的实例都是持有外部类的实例的引用,当这些匿名内部类或者非静态内部类中做耗时操作时,即使他们持有的外部类的实例已经销毁,但是,只要匿名内部类或非静态内部类的实例内部做的耗时操作未结束,就会一直持有外部类实例的引用,这样就造成内存泄漏。
正确的做法是,将非静态内部类声明成静态内部类

static class MyAsyncTask extends AsyncTask<Void,Void,Void>{
	@override
	protected void doInBackground(Void... params){
		while(true);
	}

}

或者在Activity销毁时,取消相应的任务,AsyncTask.cancel()方法,避免任务在后台执行浪费资源,进而避免内存泄漏。

private void destroyAsyncTask(){
	if(null!=asyncTask && !asyncTask.isCancelled()){
		asyncTask.cancel(true);
	}
}

@Override
protected void onDestroy() {
    super.onDestroy();
    destroyAsyncTask();
}

3.Handler使用不当造成的内存泄漏
Handler的Message被存储到MessageQueue中,有些Message并不能马上被处理,他们在MessageQueue中存在的时间很长,这就导致Handler无法被回收。如果Handler是非静态的,则Handler也会导致引用它的Activity或者Service不能被回收。
错误使用代码举例:

public class HandlerLeakActivity extends AppCompatActivity {
    private MyHandler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_leak);
        mHandler = new MyHandler();
        mHandler.sendMessageDelayed(mHandler.obtainMessage(100),60000);
    }

    private void test(){
        LogUtil.i("模拟延时任务");
    }

    private class MyHandler extends  Handler{

        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 100:
                    test();
                    break;
            }
        }
    }
}

解决方案有两种:
方案一:将Handler声明为静态实例,使用弱引用的方式,让Handler的实例持有外部类的实例的引用
正确使用方式的代码举例:

public class HandlerLeakActivity extends AppCompatActivity {
    private MyHandler mHandler;
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler_leak);
        mHandler = new MyHandler(this);
        mHandler.sendMessageDelayed(mHandler.obtainMessage(100),60000);
    }

    private void test(){
        LogUtil.i("模拟延时任务");
    }

    private static class MyHandler extends  Handler{
        private WeakReference<Activity> mWeakRef;
        private MyHandler(Activity activity){
            mWeakRef = new WeakReference<>(activity);
        }
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what){
                case 100:
                    if(null != mWeakRef && null!=mWeakRef.get()){
                        HandlerLeakActivity activity = (HandlerLeakActivity) mWeakRef.get();
                        activity.test();
                    }
                    break;
            }
        }
    }
}

使用static 修饰MyHandler是为了不让这个静态内部类隐式持有外部类HandlerLeakActivity的引用,在MyHandler中,由于要使用到HandlerLeakActivity类的方法,需要显示的持有HandlerLeakActivity的引用,但是这样就会造成内存泄漏,所以采用WeakReference方式持有HandlerLeakActivity的引用。这样就不怕内存泄漏了。如果是使用handler.post方法,为了避免内存泄漏,自定义一个MyRunnable实现Runnable接口,将这个指定的MyRunnable使用static 修饰。

方案二:
如果不想使用静态内部类和WeakReference,可以在onDestroy方法中,清除掉所有的CallBacks和Messages。

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

4.未正确使用Context导致的内存泄漏
比如一个静态单例的方法,需要传入一个Context类型的实例作为参数参入,如果在Activity中调用了这个静态单例的方式,来获取单例,此时,传入的是这个Activity自身的引用,如果,在Activity的销毁时,这个静态单例对象就会一直持有Activity的引用,导致这个Activity无法被GC回收。
错误使用代码举例:

public class LeakTestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_leak);
        CommonUtil.getInstance(this);
    }
}

public class CommonUtil {
    private static Context mContext;
    private static CommonUtil instance;
    private CommonUtil(Context context){
        this.mContext = context;
    }

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

正确使用代码举例:

public class CommonUtil {
    private static Context mContext;
    private static CommonUtil instance;
    private CommonUtil(Context context){
        this.mContext = context;
    }

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

如果不是需要显示Dialog,可以将context,替换成applicationContext,applicationContext的生命周期和应用一样长,这样就不存在内存泄漏的问题。

5.静态View
使用静态View虽然可以避免每次启动Activity都去渲染这个View,但是静态View会持有Activity的引用,
导致Activity无法被回收,解决的办法是,在Activity的onDestory方法中,将静态View置为null。
错误使用代码举例:

public class StaticViewLeakActivity extends AppCompatActivity {

    private static View tv; //静态变量textView,持有StaticViewLeakActivity的引用

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_view_leak);
        tv = findViewById(R.id.tv);
    }
}

正确使用代码:

public class StaticViewLeakActivity extends AppCompatActivity {

    private static View tv; //静态变量textView,持有StaticViewLeakActivity的引用

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_static_view_leak);
        tv = findViewById(R.id.tv);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        tv = null;
    }
}

6.WebView
不同的Android版本的WebView会有差异,加上不同的厂商定制的ROM的WebView的差异,这就导致WebView存在很大的兼容性问题。WebView都会存在泄漏的问题,在应用中如果使用一次WebView,内存就不会被释放,通常的解决办法是为WebView单开一个进程,使用AIDL与应用进行通信。WebView进程可以根据业务需求,在合适的时机进行销毁。

7.资源对象未关闭
资源对象比如Cursor,File等,往往使用了缓存技术,会造成内存泄漏,因此,在资源不适用时,一定要确保他们已经关闭并将它们的引用置为null,通常在finally语句块中进行关闭,方式异常发生时,无法及时的释放资源。

8.集合中的对象未清理
通常一些对象的引入会加入到集合中,当不需要该对象时,如果没有将它的引用从集合中清理掉,这样不仅导致集合越来越大,如果,集合是static的,那么情况会更加的糟糕。

9.Bitmap对象
临时创建的某个相对比较大的Bitmap对象,在经过转换后得到新的Bitmap对象后,应该尽快释放和回收原始的Bitmap,这样能够更加快的释放原始Bitmap所占用的空间。避免静态变量持有比较大的Bitmap对象,或者其它大的数据对象,如果已经持有,要记得尽快将该静态变量置为null

10.监听器未关闭
很多的系统服务(SensorManager,TelephonyManager)需要register和unregister监听器,包括动态广播也是,我们要确保在合适的时候去unregister监听器。自己手动添加到监听器,要记得及时的移除这个listener。

参考:

《Android 进阶解密》

Handler内存泄漏详解及其解决方案
https://blog.csdn.net/javazejian/article/details/50839443

Handler内存泄露及解决方案
https://www.jianshu.com/p/804e774d9f76

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值