android开发中比较常见的内存泄漏以及改正

具体操作代码与解释,请参考我的Github:

https://github.com/houzirui?tab=repositories


android开发中比较常见的内存泄漏以及改正-

我们一起来看一下android中常见的内存泄漏代码,这些错误我们可能在项目开发中经常碰到。

一 Handler造成的内存泄漏

平时在处理网络任务或者封装一些请求回调等api都应该会借助Handler来处理。

Handler如此常用,一个不小心,比较容易造成我们app的内存泄漏。

下面我们举个例子:

1. 新建一个工程,配置好leakCanary内存泄漏检测环境。

2. 布局好UI 。(界面布局仅一个button,activity_main.xml文件中加个Button即可,Button的ID为send_btn).

3. 在MainActity中添加如下代码

 

分析: 在项目开发中,我们经常会写上面这样的代码吧? 在一个界面中加载数据loadData,加载数据完成后,发送Message,然后在在Handler的handlerMessage()中去处理我们的消息。

运行代码,我们做如下操作:

1. 点击按钮,在20秒内点击手机返回键,关闭MainActivity

2. 等待10秒左右

 

 

 

将会发现,LeakCanary提示内存泄漏:

 

分析泄漏的原因:

由于mhandler是Handler的匿名内部类的实例,所以它持有外部类Activity的引用,我们知道消息队列是在一个Looper线程中不断轮询处理消息,而消息队列中的Message持有mHandler实例的引用,mHandler又持有Activity的引用,那么当这个Activity退出时,消息队列中还有未处理的消息或者正在处理消息,将导致该Activity的内存资源无法及时回收,引发内存泄漏。

 

处理办法: 使用静态内部类,将上面的代码改一下:

 

修改代码后运行,重复上面相同的操作,LeakCanary没有报出内存泄漏。

 

还有一个问题,在我们平时使用handlerMessage处理消息时,很多时候需要去使用外部类MainActivity的函数,比如我们需要更新MainActivity的UI 。

但是现在我们的问题时,我们静态内部类没有MainActivity的引用,没办法直接去访问MainActivity的属性和函数啊,怎么办呢 ?

 

Handler使用方式升级版:使用弱引用 -解决静态内部类访问外部类

名词解释:弱引用-----可以被JAVA 虚拟机顺利垃圾回收的一种引用方式

代码流程如下:

1.  修改一下我们刚才例子中的UI :在MainActity的布局文件activity_main.xml中添加一个TextView ,并且在MainActity的oncreat找到并初始化它。

代码和效果示意如下:

 

 

2. 我们在handlerMessage中,给TextView设置值,请注意红色方框内的弱引用使用方式

 

我们一起来分析上面的做法:

创建一个静态Handler内部类,然后对Handler持有的外部对象使用弱引用,这样在回收时也可以回收Handler持有的对象,解决了我们内存泄漏以及访问外部对象的问题。

但是,这样子还不够完美:我们退出MainActivity后,Looper线程的消息队列中还是可能会有待处理的消息,啥意思呢?就是我们MainActivity退出后,消息队列里还有消息,即我们的例子中,20秒后,还收到消息队列中的消息。

更完美的做法:我们应该在Activity关闭的时候,移除消息队列中的消息。

 

 

二 使用单例模式造成的内存泄漏

Android的单例模式在我们项目开发中经常会用到,不过使用的不恰当的话也会造成内存泄漏。因为单例的静态特性使得单例的生命周期和应用的生命周期一样长,这就说明了如果一个对象已经不需要使用了,而单例对象还持有该对象的引用,那么这个对象将不能被正常回收,这就导致了内存泄漏。

Android中习惯使用单例的常见类: xxxManager , xxxHelper , xxxUtils 等

我们举个例子:

1. 新建一个工程。

2. 配置好LeakCanary检测环境。

3. 添加一个单例类AppManager,代码如下

4 在MainActivity中使用此单例,代码如下

 

运行代码后做如下操作:

1. 点手机返回键,退出MainActivity。

2. 等待10秒

做完如上操作后,LeakCanary提示MainActivity内存泄漏:

 

 

分析一下,为什么会内存泄漏呢?

AppManager appManager=AppManager.getInstance(this);

这句传入的是Activity的Context,我们都知道,Activty是间接继承于Context的,当这Activity退出时,Activity应该被回收, 但是单例中又持有它的引用,导致Activity回收失败,造成内存泄漏。

为了以防误传Activity的Context , 我们可以修改一下单例的代码,如下:

 

这样子修改,不管外面传入什么Context,最终都会使用Applicaton的Context,而我们单例的生命周期和应用的一样长,这样就防止了内存泄漏。

修改完毕后,运行代码,重复以上操作,将会发现leakCanary没有检测出泄漏。

 

 

 

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

在实际的项目开发中,有时候我们需要频繁的启动某个页面(Activity),启动的时候总是需要初始化一些资源,为了避免重复创建相同资源,常常会使用静态对象去保存这些值,这种情况下,也很容易照成内存泄漏。我们举个例子:

1. 创建一个新的工程,配置好LeakCanary检测环境。

2. 直接在MainActivity中加入如下所示的代码

 

上面的代码中,我们创建了一个静态的资源对象mResouce,每次Activity启动都会使用该资源的数据,避免了重复创建。但是这样会造成内存泄漏。

 

运行代码,做如下操作:

1. 点击手机的返回键

2. 等待10秒

 

做上面的操作后,LeakCanary提示内存泄漏:

 

为什么会内存泄漏?

我们结合leakCanary给出的提示去分析,mResource->references->mainActivity

 1. 首先,非静态内部类默认会持有外部类的引用。

 2. 然后又使用了该非静态内部类创建了一个静态的实例。

 3. 该静态实例的生命周期和应用的一样长,这就导致了该静态实例一直会持有该Activity的引用,导致Activity的内存资源不能正常回收。

正确的做法有两种,一种是将内部类testResource改成静态内部类,还有就是将testResource抽取出来,封装成一个单例,如上一个例子那样,但是需要context时单例要切记注意Context的泄漏,使用applicationContext。

 

 

在这里,我们直接将testResource改成静态内部类。代码示意如下

 

 

修改代码,运行后执行相同操作,leakCanry没有报内存泄漏。

 

 

四 线程造成的内存泄漏

对于线程造成的内存泄漏,也是平时比较常见的,leakCanary官方Demo就是线程成造成的内存泄漏,使用了AsyncTask去执行异步线程,现在我们换个写法,直接使用Thread:

1. 新建工程,配置好leakCanary环境

2. 直接在MainActivity添加如下代码:

 

红色方框内的代码,可能每个人都这样写过。

OK ,我们执行一下,然后做如下操作:

1MainActivity页面打开后,在20秒内点击手机返回键

2. 等待10

操作完成,leakCanary检测出内存泄漏。

 

分析原因:和上面几个案例的原因类似,不知不觉又搞了一个匿名内部类Runnable,对当前Activity都有一个隐式引用。如果Activity在销毁的时候,Runable内部的任务还未完成, 那么将导致Activity的内存资源无法回收,造成内存泄漏。正确的做法还是使用静态内部类的方式,如下:

上面代码中,自定义了静态的内部类MyRunable,实现了Runable ,然后在使用的时候实例化它。

运行代码后做如上相同操作,发现leakCannary没有检测出内存泄漏。

 

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

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

 

 

总结

以上Android开发中常见的内存泄漏问题及解决办法,能对内存泄漏有一定的认识和见解,内存泄漏是很多有一定开发经验的程序员都会犯的错误,掌握这些,代表你确确实实做过一些东西,并有一定的总结。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值