使用Android Studio分析内存问题

大家好!本人是即将毕业学生一枚,闲暇时间经常看大神们写的博客学到很多东西。最近在做毕业设计的时候遇到一些问题,然后把自己的问题和解决方法总结一下,有不对的地方希望大家多多包涵,提出批评与指导。

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

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

然后上网上查了一下ImageLoader引起内存泄露的问题,发现有很多人遇到,但说到解决的办法的,并没有很好的答案。大部分都是说ImageLoaderConfiguration.Builder(context)和DisplayImageOptions.Builder()里面的配置问题,但并不能解决这个问题。还有人说换一个图片加载的框架,像Universal-Image-Loader(UIL)和Google的Volley框架,我尝试换了Universal-Image-Loader(UIL)这个,但发现还是有这个问题。而且项目换一个框架改动比较大,就没有尝试Volley框架。还有人说是应为使用了cacheInMemory(true)和cacheOnDisc(true)这两个属性导致的,但是我将他们设为false还是会引发OOM,所以就自己用工具分析。

既然产生了OOM,我们就要分析内存,看看具体是什么原因导致的。使用Android Studio工具(发现很多地方都比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方法:

它里面就是调用了BitmapFactory.decoceStream()方法然后返回了一个Bitmap对象。基本上已经可以确定占用如此多内存的就是Bitmap对象,那么我们只要将这个Bitmap释放掉就可以了。那么就接着分析看看在什么地方释放,我们看一下最上面的ImageLoader.core包下的LoadAndDisplayImageTask类的run()方法,发现里面有一个Bitmap对象,他是调用了内部tryLoadBitmap()方法给赋值的,而这个方法最后其实就是我们上面看到的调用的decodeStream方法返回的Bitmap。应为这之间的Bitmap都是方法里的局部变量所以我们不用考虑这些Bitmap的释放,那就接着向下看,发现这个Bitmap被传到了DisplayBitmapTask这个类中,那么我们再进入到这个类中看发现了一个惊喜:

这个对象被传到了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:

通过字面理解的意思就是,我们的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问题额分析与解决的过程就是这些,希望能对需要的人有帮助,通过这个问题我也学会到,遇到问题最好的解决办法就是我们要从最根本源码的角度去分析,这样既能学到很多东西,又可以解决困难问题。

时间: 2024-10-08 21:30:54

使用Android Studio分析内存问题的相关文章

Android studio 分析内存泄漏

以前用eclipse的时候,我们采用的是DDMS和MAT,不仅使用步骤复杂繁琐,而且要手动排查内存泄漏的位置,操作起来比较麻烦.后来随着Android studio的潮流,我也抛弃了eclipse加入了AS. Android Studio也开始支持自动进行内存泄漏检查,并且操作起来也比较方便. 我们大家都知道,系统是不可能将所有的内存都分配给我们的应用程序的.每个程序都会有可使用的内存上限,这被称为堆大小(Heap Size).不同的手机,堆大小也不尽相同,随着现在硬件设备不断提高,堆大小也提升

使用新版Android Studio检测内存泄露和性能

内存泄露,是Android开发者最头疼的事.可能一处小小的内存泄露,都可能是毁于千里之堤的蚁穴.  怎么才能检测内存泄露呢?网上教程非常多,不过很多都是使用Eclipse检测的, 其实1.3版本以后的Android Studio 检测内存非常方便, 如果结合上MAT工具,LeakCanary插件,一切就变得so easy了. 熟悉Android Studio界面 工欲善其事,必先利其器.我们接下来先来熟悉下Android Studio的界面  PHPer月薪测试题 [点击进入] 看看自己工资拿少

基于Android Studio的内存泄漏检测与解决全攻略

自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclipse,成为主流的Android开发IDE.Android Studio在为我们提供了良好的编码体验的同时,也提供了许多对App性能分析的工具,让开发者可以更方便分析App性能.Google在IO大会上一直告诫开发者不要无节制的使用手机内存,要注意一些不良的开发习惯会导致App的内存泄漏.虽然如今网上

使用Android Studio调试内存问题

http://blog.csdn.net/yutao52shi/article/details/50055669 前言 内存问题对于Android开发者是永远的痛.如果一个android程序员说他没有遇到过OutOfMemory,那只能说他绝对不是做Android的.以往在ADT年代,都是使用eclipse的Mat(http://www.eclipse.org/mat/)插件来做内存分析.在使用了Android Studio开发后,发现AS不仅带来了不少编码上的便利,同时还带来了很多有用的工具.

Android Studio检测内存泄露和性能

韩梦飞沙 yue31313 韩亚飞 han_meng_fei_sha [email protected] 首先需要明白一个概念, 内存泄露就是指,本应该回收的内存,还驻留在内存中. 一般情况下,高密度的手机,一个页面大概就会消耗20M内存,如果发现退出界面,程序内存迟迟不降低的话,可能就发生了严重的内存泄露. 我们可以反复进入该界面,然后点击dump java heap 这个按钮,然后Android Studio就开始干活了,下面的图就是正在dump  dump成功后会自动打开 hprof文件,

使用 Android Studio 检测内存泄漏与解决内存泄漏问题

自从Google在2013年发布了Android Studio后,Android Studio凭借着自己良好的内存优化,酷炫的UI主题,强大的自动补全提示以及Gradle的编译支持正逐步取代Eclipse,成为主流的Android开发IDE.Android Studio在为我们提供了良好的编码体验的同时,也提供了许多对App性能分析的工具,让开发者可以更方便分析App性能.Google在IO大会上一直告诫开发者不要无节制的使用手机内存,要注意一些不良的开发习惯会导致App的内存泄漏.虽然如今网上

【转载】Android Studio 设置内存大小及原理

http://www.cnblogs.com/justinzhang/p/4274985.html http://tsroad.lofter.com/post/376316_69363ae Android studio 1.0.2默认最大内存是750M,这样跑起来非常的卡,难以忍受,机器又不是固态硬盘,最后发现,这个默认值是可以修改的,在android studio目录下找到:studio64.exe.vmoptions文件,绿色部分为修改的参数(-Xmx1050m),将默认参数修改为1050M

Android Studio和MAT结合使用来分析内存问题

Android开发中时常会遇到内存泄漏的问题,而Android系统对单个App又有一定的内存限制,此值可以通过一下方式获取: ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass(); 上述代码中momeryClass的值可以当做每个App的内存限制.这个值根据不同的设备厂商都是不一样的,比如我的模拟器的值是32M,

如何才能熟练使用 Android Studio?

推荐购买<精通AndroidStudio> 编辑推荐 资深Android开发工程师根据新版Android Studio 2.2 精心打造的<精通AndroidStudio>.详细介绍了 Android Studio 实用的功能与技巧,非常全面.系统.专业.实例丰富. 内容简介 本书以通俗易懂的语言全面系统地介绍了Android Studio实用工具和操作技巧,场景明确,步骤清晰,图文结合.全书共16章.第1章对Android Studio做概要的介绍,从特性.界面.工具.功能到环境