注:本文在原文基础上在如何判断内存是否泄露方面进行了补充
安卓开发中经常出现内存溢出的情况,没有防备的开发者可能一天会不经意间写好几个内存溢出的漏洞。你可能不会发现这些漏洞,甚至都不知道它们存在,直到你看到这种异常:
java.lang.OutOfMemoryError: Failed to allocate a 4308492 byte allocation with 467872 free bytes and 456KB until OOM at dalvik.system.VMRuntime.newNonMovableArray(Native Method) at android.graphics.BitmapFactory.nativeDecodeAsset(Native Method) at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:609) at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:444) at android.graphics.drawable.Drawable.createFromResourceStream(Drawable.java:988) at android.content.res.Resources.loadDrawableForCookie(Resources.java:2580) at android.content.res.Resources.loadDrawable(Resources.java:2487) at android.content.res.Resources.getDrawable(Resources.java:814) at android.content.res.Resources.getDrawable(Resources.java:767) at com.nostra13.universalimageloader.core.DisplayImageOptions.getImageOnLoading(DisplayImageOptions.java:134)
这是啥意思呢?难道我的Bitmap太大了?
这种异常信息可能会让你产生误解。当你看到OutOfMemoryError的时候,十有八九是内存泄露。我第一次看到这个就以为我的Bitmap太大了,唉当时的我太年轻了。
什么是内存泄露?
内存泄露指程序中没有正确的释放用完的内存空间,导致运行异常或错误
Android中内存泄露是如何发生的?
事实上Android中内存泄露很容易发生。最大的问题就是Context对象。
每一个App都有一个全局的Application Context对象,可以通过getApplicationContext()方法获得。每一个Activity都是Context的子类,会存储有关当前Activity的信息。很多情况下,内存泄露与Activity泄露有关。比如有的开发者会将Context对象在各个线程里传来传去,写一些静态的TextView,让它们持有Activity的引用,这就是一个典型的错误。
判断是否有内存泄露
Memory Monitor
将设备或模拟器与Android Studio连接。
在Android Studio中运行程序。
在屏幕下方点击Android标签,再点击Memory标签,选择设备与要监控的应用。一旦Memory Monitor开始监控设备,就会出现蓝色的图表显示内存使用情况。深蓝色部分为当前应用正在使用的内存,浅蓝色(深蓝色上面那一块)为可以使用、未被分配的内存空间。
点击垃圾车图标(上图右侧那个小图标)可以触发GC(内存回收)。
有内存溢出漏洞的应用在不断操作并触发GC后的图表不断上升的:
内存泄露被解决后的应用不断操作并不断触发GC后图表波动但维持稳定:
DDMS
打开DDMS界面,在左侧面板中选择要观察的进程,点击左上角的Update Heap按钮(红圈圈出的圆柱体按钮),再点击右侧面板中的Heap标签,然后不断操作并持续点击Cause GC按钮,观察表中data object的Total Size的数值变化。如果持续增高而不下降,那就很可能是有内存泄露发生了。
有内存泄露时,应用不会重新得到用过的内存空间,当用到300MB的时候,OutOfMemoryError就发生了。从修复了内存泄露的应用的图表可以看出,应用可以回收一些内存,并处在长期稳定状态。
如何避免内存泄露?
不要将Context对象传给activity与fragment以外的对象。
永远不要将Context和View存储在静态变量中。
private static TextView textView;//不要这么干 private static Context context;//不要这么干
在onPause()/onDestroy()方法中解除监听器,包括在Android自己的Listener,Location Service或Display Manager Service以及自己写的Listener。
不要在后台线程与AsyncTask中存储activity的强引用。不然当Activity被关闭后,由于AsyncTask仍在执行且持有Activity的强引用,导致Activity无法被回收。
使用Application Context而不是Activity的Context
尽量不要用非静态内部类,因为它会持有外部类的引用。在非静态内部类中存储Activity或View的引用会导致内存泄露。如需存储就使用WeakReference。
如何解决内存泄露?
解决内存泄露需要大量的错误积累与练习。内存泄露可能会很难追踪,但借助一些工具可以帮助你识别可能的漏洞。这里我们使用MAT进行内存泄露的检测。
打开Android Studio,点击下方的Android Monitor标签。
运行应用,并选择应用来监控。
反复进行某个可能存在内存泄露的操作,比如返回打开关闭某个Activity。
在抛出OutOfMemoryException之前点击Android Monitor中的Memory标签。此时图表开始绘制,过一会点击GC图标。(刚才说的那个红色垃圾车图标)
点击垃圾车下面那个有绿色箭头的图标”Dump Java Heap”,并等待几秒。然后会生成一个.hprof文件,这个文件就是用来分析内存使用的。
下载MAT。
使用sdk中platform-tools路径下的hprof-conv文件将刚才生成的.hprof文件转码成MAT可以解析的文件。
./hprof-conv path/file.hprof exitPath/heap-converted.hprof
使用MAT打开转码后的文件。选择”Leak Suspects Report”然后点击”Finish”。
点击界面上方三条竖杠的图标。而后你会看到占用内存的对象的列表。
这个列表可能难以理解,你可以输入类名来过滤结果。我建议在输入框中输入package name,
现在我们可以看到VideoDetailActivity的9个实例,这显然不对,因为我们只应该有一个。我们可以右键单击这一条目并选择”Merge Paths to Shortest GC Root”并点击“exclude all phantom/weak/soft etc.references”来看是什么持有VideoDetailActivity的引用。
而后持有引用的的线程就会显示出来。而后你就可以深入观察什么持有了Activity的引用。
通过下面的信息我们可以看到有一个DisplayListener被注册了但从未解除。
所以这个漏洞就找到了,只需要解除监听器就可以了。
不是所有的内存泄露漏洞都这么容易找到。希望这篇文章可以帮你避免内存泄露并在内存泄露发生时找到漏洞。