1. 单例模式引起的内存泄漏
单例的生命周期往往就是跟我们应用的生命周期是一致的。如果你往这个单例里面传了一个生命周期较短的对象,比如说Activity,此时这个Activity的生命周期就会一直被单例所持有,如果一直被持有了,结果就是这个activity内存泄漏。
如果单例的时候,一定要小心,如果一定要传一个上下文,思考清楚,它的上下文的生命周期,它的时间长度是怎样的。
参考解决办法:
1、一般在我们开发的应用中,都会实现Application,在里面做一些全局性的事情。可以在该实现里面对外提供一个单例,通过此实例来获取ApplicationContext。代码如下:
2、重构Singleton,把构建单例时的context去掉,避免外面使用的人传入错误参数,代码如下:
2. 静态变量引起的内存泄漏(非静态内部类创建静态实例引起的内存泄漏)
静态变量的生命周期是很长很长的,跟应用的jvm的生命周期是一样的,也就是跟Application存在的时间是一样的。所以在我们使用静态变量的时候,一定要思考清楚,能不用静态变量就不要用静态变量。如果要用,需要思考这个静态变量会占用使用过程中多少内存,是否有替代方案。
下方就是一个内存泄漏
3. Handler引起的内存泄漏
Android特色的内存泄漏,handler会有一个持有链,它背后会有线程,有loop,有MessageQueue,有Message还有Handler本身。所以在这个里面,每一个Handler如果是以匿名内部类的方式定义的,那么这个匿名内部类会持有Activity,然而我们发送Message的时候,我们的Message又会持有Handler,而Message又会被MessagesQueue所持有,而MessageQueue又被loop持有,loop又是对应一个线程的一个死循环,所以它是不回退出的,这个时候如果Message不退出,Message不执行,意味着它持有的Handler不会释放,Handler又持有链Activity,Activity就这样被泄漏了。
4. 匿名内部类引起的内存泄漏
匿名内部类会持有外部类对象(java特点),匿名内部类进行了一个长时间的运行,比如说在匿名内部类里进行了一个耗时的动作,那么这个时候匿名内部类就不会退出,它所持有的这个Activity上下文就不会退出,这样的结果就是内存泄漏,所以当我们使用匿名内部类的时候,一定要注意,匿名内部类是不是持有了外部类对象,而外部类是不是又持有了一些其他的类,比如说Activity持有的一个类,然后这个Activity又被另外一个类持有,在另外一个类里面它又存在着一个匿名内部类。
这个链条的持有关系就导致了我们去查找这样的内存泄漏时会很吃力。因为不是简单直接使用的。
5. 资源的未释放导致的内存泄漏
在Android里,我们所讲的资源,比如说数据库的打开有没有关闭,文件file打开有没有关闭,流打开有没有关闭,Bitmap是否有释放,这一切东西都寓意着资源的释放。因为在Android里,我们为了让用户第一时间看到需要的信息,我们往往会在这一系列文件,一系列资源的背后,都给它设计一个缓存机制,因为用户第一时间看到就可以直接在内存里面找到缓存,在缓存里就可以拿到数据,拿到数据就可以快速的展示,然而这种缓存机制,提升了用户的体验,同时也给开发这带来很大的痛苦。因为这种缓存机制往往有些时候不是java可以解决,可以自动释放的,因为它会涉及到底层c/c++的释放,所以我们需要对这一系列的需要释放的资源,必须在我们的生命周期结束的时候,比如Activity或者Fragment的onDestroy onPause里面去把这些资源进行释放,尤其是音视频播放的时候请大家一定要注意,释放资源就成了我们释放内存非常重要的一个点。
6. 注册与反注册引起的内存泄漏
我们往往在Android的OnCreateView或者说onResume的时候,我们可能会去初始化注册一个广播,注册一个EventBus, 这些都需要我们unRegister,进行注销。我们往往会在生命周期的onDestroy里面进行,哪怕onDestroy生命周期你什么都可以不干,但是一定要把onDestroy这个函数实现了,实现之后,再去对应的生命周期去看onCreate里面干了什么事情,是否可以把这些资源进行释放,是否可以进行unRegister这些资源。
7. Context引起的内存泄漏
Activity的Context,fragment也可以拿到Context,Application也Context,第三方的架构经常传一个Context进去,这个Context意味着我就是把这一个fragment或者application它的所有的上下文资源全部传给了这些第三方的库,如果这些第三方的库如果不能自动的去释放这些库,就产生了内存泄漏。所以当我们在第三方里面需要传一个上下文Context时,要注意,这一个Context的生命周期什么时候用,什么时候释放,只有掌握了这些,在第三方sdk里面Context应该怎么样使用的时候,我们才能传一个正确的生命周期所对应的Context,所以我们使用的时候一定是取短不取长,这是原则,哪怕有可能带来空指针异常的问题,这个问题是第三方sdk不严谨导致的,暴露了第三方sdk的问题,当然我们常用的glide已经规避了这些问题。
8. 集合引起的内存泄漏
ArrayList, HashMap, linkedlist, 这些地方往往会带来内存泄漏,比如说一个fragment,用viewPager来持有一系列的fragment,那么这一系列fragment就会被activity持有,这个activity通过arraylist持有了一系列fragment,arraylist如果没有被释放,则fragment会一直被持有,虽然走了它生命周期结束,但是它仍旧被activity持有的这个arryalist持有。所以当我们需要使用arraylist时,不要将一个内存释放交给jvm去完成,一定要慎重的去思考arraylist,HashMap, linkedlist,它们应该在什么时候进行一个数据的clear,如果能够在destroy里面进行clear的话,一定要自己在destroy里面去进行clear。如果在这arrayList前加一个static就完蛋了,往往这些问题在开发中很难找到。比如说你在ViewPager里面使用了一个static的静态的arrayList,用这个arraylist持有了fragment, 那这就基本上完蛋了。因为这样界面显示都会成问题。
内存优化工具 LeakCanary
LeakCanary是一个非常好用的检测内存泄漏的一个工具。