在Android中解决内存溢出 – OutOfMemoryError

原文链接:http://riggaroo.co.za/fixing-memory-leaks-in-android-outofmemoryerror/

注:本文在原文基础上在如何判断内存是否泄露方面进行了补充

安卓开发中经常出现内存溢出的情况,没有防备的开发者可能一天会不经意间写好几个内存溢出的漏洞。你可能不会发现这些漏洞,甚至都不知道它们存在,直到你看到这种异常:

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

  1. 将设备或模拟器与Android Studio连接。

  2. 在Android Studio中运行程序。

  3. 在屏幕下方点击Android标签,再点击Memory标签,选择设备与要监控的应用。一旦Memory Monitor开始监控设备,就会出现蓝色的图表显示内存使用情况。深蓝色部分为当前应用正在使用的内存,浅蓝色(深蓝色上面那一块)为可以使用、未被分配的内存空间。

    演示图片

  4. 点击垃圾车图标(上图右侧那个小图标)可以触发GC(内存回收)。

有内存溢出漏洞的应用在不断操作并触发GC后的图表不断上升的:

有内存泄露

内存泄露被解决后的应用不断操作并不断触发GC后图表波动但维持稳定:

无内存泄露

DDMS

打开DDMS界面,在左侧面板中选择要观察的进程,点击左上角的Update Heap按钮(红圈圈出的圆柱体按钮),再点击右侧面板中的Heap标签,然后不断操作并持续点击Cause GC按钮,观察表中data object的Total Size的数值变化。如果持续增高而不下降,那就很可能是有内存泄露发生了。

DDMS

有内存泄露时,应用不会重新得到用过的内存空间,当用到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进行内存泄露的检测。

  1. 打开Android Studio,点击下方的Android Monitor标签。

  2. 运行应用,并选择应用来监控。

  3. 反复进行某个可能存在内存泄露的操作,比如返回打开关闭某个Activity。

  4. 在抛出OutOfMemoryException之前点击Android Monitor中的Memory标签。此时图表开始绘制,过一会点击GC图标。(刚才说的那个红色垃圾车图标)

    hprof1

  5. 点击垃圾车下面那个有绿色箭头的图标”Dump Java Heap”,并等待几秒。然后会生成一个.hprof文件,这个文件就是用来分析内存使用的。

  6. 下载MAT

  7. 使用sdk中platform-tools路径下的hprof-conv文件将刚才生成的.hprof文件转码成MAT可以解析的文件。

     ./hprof-conv path/file.hprof exitPath/heap-converted.hprof
    
  8. 使用MAT打开转码后的文件。选择”Leak Suspects Report”然后点击”Finish”。

    hprof2

  9. 点击界面上方三条竖杠的图标。而后你会看到占用内存的对象的列表。

    hprof3

  10. 这个列表可能难以理解,你可以输入类名来过滤结果。我建议在输入框中输入package name,如下:

    hprof4

  11. 现在我们可以看到VideoDetailActivity的9个实例,这显然不对,因为我们只应该有一个。我们可以右键单击这一条目并选择”Merge Paths to Shortest GC Root”并点击“exclude all phantom/weak/soft etc.references”来看是什么持有VideoDetailActivity的引用。

    hprof5

    而后持有引用的的线程就会显示出来。而后你就可以深入观察什么持有了Activity的引用。

  12. 通过下面的信息我们可以看到有一个DisplayListener被注册了但从未解除。

    hprof6

所以这个漏洞就找到了,只需要解除监听器就可以了。

不是所有的内存泄露漏洞都这么容易找到。希望这篇文章可以帮你避免内存泄露并在内存泄露发生时找到漏洞。

参考文献:

Memory Monitor Walkthrough
Android最佳性能实践(二)——分析内存的使用情况

2 thoughts on “在Android中解决内存溢出 – OutOfMemoryError

Leave a Reply

Your email address will not be published. Required fields are marked *