内存泄漏之我见

简介

   内存泄漏用一句话总结的话就是 应该被释放的内存没有得到释放,生命周期长的引用了生命周期短的

       内存泄漏对程序造成的影响就是OOM,让程序Crash,因为Android系统为每个应用程序分配的内存是有限的,而当一个应用中产生的内存泄漏比较多时,这就会导致应用所需要的内存超过系统分配的内存限额,这就造成了内存溢出从而导致应用Crash。

       因为内存泄漏是在堆内存中,所以对我们来说并不是可见的。通常我们可以借助MAT、LeakCanary等工具来检测应用程序是否存在内存泄漏。MAT是一款强大的内存分析工具,功能很多但使用特别繁琐,需要分析heapdump.hprof文件,相对来说LeakCanary就简单实用很多,它的原理是手动触发GC 然后分析强引用的GC引用链

重点:

今天我们着重看一下常见的内存泄漏以及解决方法

1、单例造成的内存泄漏

由于单例的静态特性使得其生命周期和应用的生命周期一样长,如果一个对象已经不再需要使用了,而单例对象还持有该对象的引用,就会使得该对象不能被正常回收,从而导致了内存泄漏。

示例:防止单例导致内存泄漏的实例

public class MySingleton{
    private static MySingleton instance;
    private Context context;
    private MySingleton(Context context) {
        this.context = context;
    }
public static MySingleton getInstance(Context context) {
    if (instance == null) {
        instance = new MySingleton(context);
     }
        return instance;
  }
}

这是一个普通的单例模式,当创建这个单例的时候,由于需要传入一个Context,所以这个Context的生命周期的长短至关重要:

1、如果此时传入的是 Application 的 Context,因为 Application 的生命周期就是整个应用的生命周期,所以这将没有任何问题。

2、如果此时传入的是 Activity 的 Context,当这个 Context 所对应的 Activity 退出时,由于该 Context 的引用被单例对象所持有,其生命周期等于整个应用程序的生命周期,所以当前 Activity 退出时它的内存并不会被回收,这就造成泄漏了。

修改办法为:

 private MySingleton() {
        //将构造函数里的Context改为应用程序类,并且在获取单例对象时不传入任何Context
        this.context = MyApplication.getContext();
    }

2、非静态内部类创建静态实例造成的内存泄漏

public class MainActivity extends AppCompatActivity {
    //非静态内部类创建了静态实例
    private static NonStaticClass sNonStaticClass = null;

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

        if (sNonStaticClass == null) {
            sNonStaticClass = new NonStaticClass();
        }
    }

    private class NonStaticClass {
    }
}

因为非静态内部类默认会持有外部类的引用,而该非静态内部类又创建了一个静态的实例,该实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,从而导致Activity的内存资源不能被正常回收。

解决办法:将该内部类设为静态内部类或将该内部类抽取出来封装成一个单例

3、Handler造成的内存泄漏

当我们在Activity或者Fragment中写如下代码时,Lint自动会提醒我们说这个类应该被静态修饰不然就会由内存泄漏发生

这个错误算是很常见的,下面贴出一个示例代码:

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

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // do something...
                handler.sendEmptyMessage(0x123);
            }
        });
    }
}

我们知道在主线程中创建一个Handler,它会自动关联主线程的Looper也就是MainLooper,而主线程的Looper生命周期和整个应用生命周期一样长,另一个原因是Handler同时会自动关联Looper的MessageQueue,这样所有发送到MessageQueue的消息都会持有Handler的引用,提示说要设置成static 的是从java代码检查的角度来看,非静态内部类和匿名类内部类都会潜在持有它们所属的外部类的引用,但是静态内部类却不会

解决办法:将Handler设置为静态内部类或者独立出来

4、线程造成的内存泄漏

asyncTask、Thread 的runnable定义成非静态内部类的时候,当Activity想要被回收的时候,由于任务持有了 Activity的隐式引用,任务没有完成导致当前Activity没办法被回收而导致内存泄漏

public class MainActivity extends AppCompatActivity {

    private MyAsyncTask mMyAsyncTask;
    private MyRunnable mMyRunnable;

    class MyRunnable implements Runnable {
        @Override
        public void run() {
            //do something...
        }
    }

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mMyAsyncTask = new MyAsyncTask(this);
        mMyAsyncTask.execute();

        new Thread(new MyRunnable()).start();
    }

    static class MyAsyncTask extends AsyncTask<Void, Void, Void> {
        private WeakReference<Context> weakReference;

        public MyAsyncTask(Context context) {
            weakReference = new WeakReference<>(context);
        }

        @Override
        protected Void doInBackground(Void... voids) {
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            super.onPostExecute(aVoid);
            MainActivity activity = (MainActivity) weakReference.get();
            if (activity != null) {
                //do something..
            }
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mMyAsyncTask.cancel(true);;//销毁后台可能在执行的任务,节省资源
    }
}

解决办法:定义为静态内部类,避免Activity内存资源的泄漏

5、WebView造成的泄露

Webview解析网页的时候会申请native堆内存来保存页面的元素。页面越复杂占用的内存越多,当收集加载网页过量的时候就会非常卡顿甚至app闪退 等

public class MainActivity extends AppCompatActivity {
    private WebView mWebView;
    @Override
    public void onCreate(@Nullable Bundle savedInstanceState, @Nullable PersistableBundle persistentState) {
        super.onCreate(savedInstanceState, persistentState);
        //...
    }
    @Override
    protected void onDestroy() {
         if (mWebView != null) {
            mWebView.pauseTimers();
            mWebView.removeAllViews();
            mWebView.destroy();
        }
      
        super.onDestroy();
    }
}

解决办法:将WebView所处的Activity放在一个单独的进程当中,检查到app占用内存过多的时候主动杀掉这个进程,系统就会自动回收这部分内存,或者开启新的进程后通过AIDL与主线程进行通信,并根据业务的需求选择合适的时机进行销毁。杀掉当前进程的代码如下:

android.os.Process.killProcess((android.os.Process.myPid()));

6、资源未关闭造成的内存泄漏

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

  1. 在Activity中register了一个BraodcastReceiver,但在Activity结束后没有unregister该BraodcastReceiver。
  2. 资源性对象比如Cursor,Stream、File文件等往往都用了一些缓冲,我们在不使用的时候,应该及时关闭它们。在我们的程序退出时一定要确保我们的资源性对象已经关闭。

 

7、集合容器中的内存泄露

我们常常忽略的一点,当用到集合的时候我们通常把一些对象的引用加入到了集合容器(比如ArrayList)中,但是我们不用的时候却没有把它们从集合中清理掉,如果这个集合是static的话,结果可想而知,就算不是static的,如果大量数据存储也是很恐怖的。

解决方法:在退出程序之前,将集合里的东西clear,然后置为null,再退出程序。并合理的替换使用Android为我们提供的SparseArray和ArrayMap。

总结:从上面的各种方法我们也看到了,内存泄漏大部分是由一个生命周期引发的惨案啊。所有我们要相近一切办法把一个类的范围缩小,这也符合最少知道原则和接口隔离原则。

1、在涉及使用Context时,对于生命周期比Activity长的对象应该使用Application的Context。凡是使用Context优先考虑Application的Context。实在不行,那就用弱引用

2、需要在静态内部类中使用非静态外部成员变量(如:Context、View ),可以在静态内部类中使用弱引用来引用外部类的变量来避免内存泄漏。

3、对于不再需要使用的对象,显示的将其赋值为null,比如资源文件记得使用完后释放。

4、注意单例、静态对象、全局性集合等的生命周期。使用的时候要考虑清楚是否引入了比自己生命周期短的对象

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值