Android内存泄漏场景

首先咱们提出一个问题,什么是内存泄漏? 内存泄漏,通俗得来说就是“没有用的对象没法被回收”

而后咱们再提出一个问题,内存泄露会致使什么状况?
确定是内存溢出,而后程序崩溃啊!html

区别

相信初学者可能不太清楚内存溢出和内存泄漏的区别。java

  • 内存溢出:程序使用的空间大于本来系统给它申请的空间。
  • 内存泄漏:在new了对象以后,没有使用这个对象了,可是又没有被回收,一直占用着内存。

储备知识

要想了解内存泄露的知识,首先咱们要清楚如下的知识点android

  • Java的GC(Garbage Collection,垃圾回收)机制。
  • Java的内存管理机制

    详情能够见 浅谈垃圾回收算法

简单判断

如何判断,只用记住一点:A类实例引用B类实例,而A类实例的生命周期长于B类实例的生命周期。git

泄漏场景

在Android开发中,内存泄漏的地方仍是挺多的,有时候稍不注意就写出了一个内存泄漏的代码。因此说咱们要熟记哪些地方容易发生内存泄漏,在代码Review的状况下很容易检查出来。github

单例引发的内存泄漏

单例模式使用的地方很是多,它的生命周期经常伴随着App的一辈子,因此说也十分容易形成内存泄漏。

例如单例模式中引用Activity的Context,而单例模式的生命周期长于Activity。这里单例模式引用Activity的实例,当Activity被销毁,Activity没法被回收,形成内存泄露。
算法

public class Single {
  private static Single instance;
  private Context context;
  private Single(Context context) {
    this.context = context;
  }
  public static Single getInstance(Context context) {
    if (instance == null) {
        instance = new Single(context);
    }
    return instance;
  }
}
复制代码

值得一提的是,若是这里引用的Application的Context,将无任何影响。由于Application的生命周期与单例模式一样长。bash

集合的内存泄漏

在静态集合里面添加对象,添加完成以后该集合将会一直引用此对象,该对象没法被释放。(不过咱们也写不出这样沙雕的代码来!app

static List<Object> objectList = new ArrayList<>();
   for (int i = 0; i < 10; i++) {
       Object obj = new Object();
       objectList.add(obj);
       obj = null;
    }
复制代码

解决方法:在使用完该集合以后,将集合清空。ide

匿名内部类以及非静态内部类

特色:匿名类和非静态内部类都持有外部类的引用工具

匿名内部类
Handler泄漏

匿名内部类引发的内存泄露,最典型的例子就是Handler泄漏。当Handler的消息没有发送完毕,Activity就被销毁了,此时Activity没法被即时回收。

public class MainActivity extends Activity{
    
    @SuppressLint("HandlerLeak")
    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
           //do something...
        }
    };
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}
复制代码

如何解决Handler泄漏呢?咱们用static修饰Handler,这样Hanlder的生命周期就与Activity无关了。若是想引用Activity实例,这里能够用一个弱引用来获取。或者能够在Activity 的onDestroy() 方法中移除全部的消息 handler.removeCallbacksAndMessages(null);

public class MainActivity extends Activity{
    private final MyHandler handler = new MyHandler(this);
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
    
    
    private static class MyHandler extends Handler {
        private final WeakReference<MainActivity> mActivity;

        public MyHandler(MainActivity activity) {
            mActivity = new WeakReference<MainActivity>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
          MainActivity activity = mActivity.get();
        }
  }
}
复制代码
Thread泄漏

在Activity中new Thread时,若是在子线程作耗时操做,当Activity被销毁后,子线程的工做并未完成,此时会内存泄漏。

public class MainActivity extends Activity{
    
    @Override
    public void onCreate(Bundle savedInstanceState){
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(100000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}
复制代码

这里一样能够继承 Thread 实现静态内部类来解决。

static修饰的成员变量

若是成员变量被声明为 static,其生命周期将与整个app进程生命周期同样。

Stream未关闭

在调用了流以后,必定要记得关闭流。用到流的地方通常都是文件操做,虚拟机没法经过垃圾回收来释放这些资源。

其余泄漏

例如service忘记解除绑定,broadcastReceiver忘记解除订阅,EventBus忘记解除订阅等。

经常使用的检测内存泄漏的工具

光凭肉眼咱们其实只能找出比较明显的内存泄露点,还有许多隐藏得比较深的内存泄露。那么咱们如何找到这些点呢?固然是利用工具。

  • Android Lint:Android Studio提供的代码扫描分析工具
  • Leakcanary: Square 公司开源的「Android 和 Java 的内存泄漏检测库」

总结

在Android系统中,每一个App最多能分配大约只有100-200MB的内存空间,由于内存不够,溢出而引发的程序崩溃仍是不在少数。因此说,平常开发中仍是要千万注意内存泄露。