使用Android Studio分析内存问题

使用Android Studio分析内存问题

这篇博文主要介绍使用AndroidStudio对内存进行分析和跟踪,还有就是从源码角度解决ImageLoader引起的OOM问题。

我正在做的项目使用到了ImageLoader来加载图片,我也是第一次使用,就拿来直接用了。写完的代码运行很正常的加载图片,并没有发现什么问题。但是在测试的时候发现了问题。当多次打开软件中一个浏览大图片的Activity(每次的图片都不一样)后,这时在这个Activity中点击返回后,屏幕会突然变黑,然后回到了MainActivity,并没有回到上一级Activity,甚至连之间打开的多个Activity都没有返回。想了一下问题怀疑可能是重启了这个应用。但那个大图片的Activity开始打开的几次都可以正常的回到上一级Activity,再多打开几次就不能正常回退了,这时候我就开始看LogCat打出的日志,看看有没有发生什么异常。发现ImageLoader产生了OOM的Bug。如下图:

OOM

然后上网上查了一下ImageLoader引起内存泄露的问题,发现有很多人遇到,但说到解决的办法的,并没有很好的答案。大部分都是说ImageLoaderConfiguration.Builder(context)和DisplayImageOptions.Builder()里面的配置问题,但并不能解决这个问题。还有人说换一个图片加载的框架,像Universal-Image-Loader(UIL)和Google的Volley框架,我尝试换了Universal-Image-Loader(UIL)这个,但发现还是有这个问题。而且项目换一个框架改动比较大,就没有尝试Volley框架。还有人说是应为使用了cacheInMemory(true)和cacheOnDisc(true)这两个属性导致的,但是我将他们设为false还是会引发OOM,所以就自己用工具分析。
既然产生了OOM,我们就要分析内存,看看具体是什么原因导致的。使用AndroidStudio工具(发现很多地方都比eclipse强大),在使用AndroidStudio的Memory工具观察的时候发现了问题,在我多次打开这个Activity的时候,发现Memory在一直的增长,每次Activity退出后Memory也没有下降。

内存上升图就这样当我多次浏览不同的图片,多次打开这个Activity后,这个Memory就一直的增长,当增长到120+M的时候应用突然挂掉,之后又自动重启。发现了这个现象我们就可以借助AndroidStudio的强大工具来分析导致这个问题的原因。在Memory窗口的左边有四个按钮,分别是:

Enabled(蓝色的开关):就是一个正常的开关功能
Initiate GC(橙色小卡车):就是手动调用GC,我们在抓内存前,一定要手动点击 Initiate GC按钮手动触发GC,这样抓到的内存使用情况就是不包括Unreachable对象的(Unreachable指的是可以被垃圾回收器回收的对象,但是由于没有GC发生,所以没有释放,这时抓的内存使用中的Unreachable就是这些对象)
Dump Java Heap(紫色带向下的箭头):获取hprof文件(hprof文件是我们使用MAT工具分析内存时使用的文件),但这里直接产生的文件MAT还不能直接使用,需用转换成标准的hprof文件。可以使用AndroidStudio转换或者用hprof-conv命令转化,网上可以查到。
Start Allocation Tracking(紫色带圆圈):开始分配追踪,第一次点击可以指定追踪内存的开始位置,第二次点击可以结束追踪的位置。这样我们截取了一段要分析的内存,等待几秒钟AndroidStudio会给我们打开一个Allocation视图(感觉和MAT工具差不多,不过MAT工具更加强大,我们也可以获取hprof文件,使用MAT来分析)例如下图:

我截取了这段内存,接下来我们就看Allocation视图来分析。

\

通过视图中的内容我们首先看到1号线程它占用了百分之92的内存,那么我们就点开它看一下,每次都只要点开其中内存占用最大的就可以。

\



到划红线的那个位置就可以了,这里已经到了安卓的graphics源码的层次,所以我们不需要再向下看了。我们看一下红线上面的ImageLoader里的那个decodeStream方法:
<img alt="decodeStream" src="http://www.2cto.com/uploadfile/Collfiles/20160506/20160506090822425.png" title="" kf="" ware="" vc="" "="" target="_blank" class="keylink" style="border-width: 0px; padding: 0px; margin: 0px auto; list-style: none; display: block; width: 630px; height: 118.462px;">vcHLRGlzcGxheUJpdG1hcFRhc2vV4rj2wODW0KOsxMfDtM7Sw8fU2b34yOu1vdXiuPbA4NbQv7S3os/WwcvSu7j2vqrPsqO6PC9wPg0KPHA+PGltZyBhbHQ9"DisplayBitmapTask" src="/uploadfile/Collfiles/20160506/20160506090822426.png" title="\" />

这个对象被传到了DisplayBitmapTask的成员变量中,DisplayBitmapTask本身也实现了Runnable接口,那么我就想是不是应为DisplayBitmapTask这个类没有及时回收导致的Bitmap成员变量占用内存的问题,所以我就在run()方法跑完之后,让Bitmap = null。然后又将程序运行了一遍,但是发现根本没有解决这个问题。这时候又重头想了一下整个过程,发现我的操作是不对的,虽然我成功将DisplayBitmapTask中的Bitmap成员变量置为了空,但是他只是真正的Bitmap内存的一个引用,我并没有将真正的Bitmap内存空间所释放,只是将他的一个引用给释放了。通过网上查询发现,Bitmap会保存在C层的内存中,如果我们想要释放他在C层中的内存,可以调用Bitmap的recycle()方法,从代码中看,这个方法本身会调用Bitmap.cpp中的Bitmap_recycle方法,这个是native方法。这样就可以通知底层将C层中的Bitmap内存释放掉,而且还会将一些相关的引用计数置0。但是并不会立即释放掉,这要看系统。 一下子完美解决。既然查到了原因,那就好办了,我将Bitmap = null这句话改为Bitmap.recycle()。好了,接下来我再次运行,发现这回应用直接就崩了,只要能在Logcat中抓到Log,一切问题都好解决。看了一下log发现下面这个Error:

error

通过字面理解的意思就是,我们的View去显示了已经recycle的Bitmap。仔细想一想确实是,我们在ImageLoader中的一个线程的run方法中直接将Bitmap释放掉了,那我们的应用岂不是直接显示一个空的Bitmap,这当然会引起错误。既然不能在这里释放掉Bitmap,那我们应该在什么地方释放呢?那一定是在我们的View不再使用这个Bitmap的时候调用,想一想,那我们只要在Activity或者fragment的onDestory()方法中释放了不就可以了吗?但这时又有一个问题产生,我们该如何在上层应用获取到这个View用到的Bitmap的对象呢?想了半天发现好像只能通过改写ImageLoader的源码来想办法将这个对象会传到上层应用。正当我在ImageLoader中寻找在哪里写这个回调方法时,突然我看到了这段代码:

这里写图片描述



发现其实ImageLoader的源码中已经为我们写好了这个回调监听器接口,而且在ImageLoader的displayImage方法中,也重载了一个提供了传递ImageLoaderListener实现对象的方法。这样我们只需要实现ImageLoaderListener这个接口,并实现他的onLoadingComplete方法,在这个方法中对Bitmap做处理就可以了。

这里写图片描述

我们只需要在Activity或Fragment的onDestory()方法中再调用cleanBitmapList()方法就可以了。通过上面的更改我再次运行应用,发现真的解决了这个问题,我的应用内存不再会是一直的上升,而是上升之后又会下降。如图:

这里写图片描述

注:这个方式比较适合cacheInMemory(false)和cacheOnDisc(false)的情况写,应为如果你将这两个设置为true时,那么下次再次加载图片时他会从内存和硬盘中去加载图片,这两个地方也持有Bitmap的引用,这时就会再次出现trying to use a recycled bitmap 这个错误。当然你也是可以设置为true的,只要在清除Bitmap(也就是我们这里调用cleanBitmapList)的地方再加上mImageLoader.clearDiscCache()和mImageLoader.clearMemoryCache()这两句话就可以。

好了,整个ImageLoader引起的OOM问题额分析与解决的过程就是这些,希望能对需要的人有帮助,通过这个问题我也学会到,遇到问题最好的解决办法就是我们要从最根本源码的角度去分析,这样既能学到很多东西,又可以解决困难问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值