Android应用内存不足的问题-尝试了一切,但仍然很茫然


87

我花了整整4天的时间竭尽所能找出我正在开发的应用程序中的内存泄漏,但是很久以前事情就没有了。

我正在开发的应用具有社交性质,因此请考虑对活动(P)进行配置,并列出带有数据的活动-例如徽章(B)。您可以从个人资料跳到徽章列表,其他个人资料,其他列表等。

因此,假设像这样的流程P1-> B1-> P2-> B2-> P3-> B3,等等。为了保持一致,我正在加载同一用户的配置文件和徽章,因此每个P页面都是相同的,因此每个B页。

问题的基本要点是:在导航了一段时间之后,根据每个页面的大小,我在随机的位置(位图,字符串等)收到了内存不足的异常,这似乎并不一致。

在尽一切可能找出导致内存不足的原因之后,我什么都没想到。我不明白的是,如果Android在加载时内存不足而崩溃,为什么Android不会杀死P1,B1等。如果我曾经通过onCreate()和onRestoreInstanceState()返回到这些早期活动,我希望它们会死掉并复活。

更不用说了-即使我执行P1-> B1->后退-> B1->后退-> B1,我仍然会崩溃。这表明存在某种内存泄漏,但是即使在转储hprof并使用MAT和JProfiler之后,我也无法查明。

我已禁止从网络上加载图像(并增加了加载的测试数据来弥补它并使测试公平),并确保图像缓存使用SoftReferences。Android实际上试图释放它拥有的一些SoftReference,但要在它崩溃到内存不足之前。

徽章页面从Web获取数据,将其从BaseAdapter加载到EntityData数组中,然后将其提供给ListView(我实际上是在使用CommonsWare的出色MergeAdapter,但是在此Badge活动中,无论如何实际上只有1个适配器,但是我想以任何一种方式提及这个事实)。

我遍历了代码,却找不到任何可能泄漏的内容。我清除并清空了所有可以找到的内容,甚至将System.gc()左右移走,但应用程序仍然崩溃。

我仍然不明白为什么堆栈中不活动的活动不会获得收益,我真的很想弄清楚这一点。

在这一点上,我正在寻找任何提示,建议,解决方案……任何可能有用的方法。

谢谢。


因此,如果我在过去20秒钟内打开了15个活动(用户正在经历非常快的速度),那可能是问题所在吗?在活动显示后,我应该添加哪一行代码来清除活动?我得到一个outOfMemory错误。谢谢!
Ruchir Baronia

Answers:


109

我仍然不明白为什么堆栈中不活动的活动不会获得收益,我真的很想弄清楚这一点。

这不是工作方式。唯一会影响活动生命周期的内存管理是所有进程中的全局内存,因为Android决定它的内存不足,因此需要杀死后台进程以获取回收。

如果您的应用程序坐在前台开始进行越来越多的活动,那么它永远不会进入后台,因此在系统几乎终止其进程之前,它将始终达到其本地进程内存限制。(并且当它杀死进程时,它将杀死托管所有活动的进程,包括当前处于前台的所有活动。)

因此,在我看来,您的基本问题是:您要同时运行太多活动,并且/或者这些活动中的每一项都占用太多资源。

您只需要重新设计导航,就不必依靠堆积任意数量的潜在重量级活动。除非您在onStop()中做了大量工作(例如调用setContentView()来清除活动的视图层次结构并清除其可能持有的其他变量),否则您将耗尽内存。

您可能要考虑使用新的Fragment API,用一个可以更紧密地管理其内存的单个活动来替换此任意活动堆栈。例如,如果您使用片段的后退堆栈工具,则当片段进入后退堆栈且不再可见时,其onDestroyView()方法将被调用以完全删除其视图层次结构,从而大大减少了其占用空间。

现在,只要您在按回车键,进入某个活动,按回车键,进入另一个活动等的流程中崩溃,再也没有一个很深的堆栈,那么是的,您只是一个漏洞。这篇博客文章介绍了如何调试泄漏:http : //android-developers.blogspot.com/2011/03/memory-analysis-for-android.html


47
在OP的辩护中,这不是文档所建议的。引用developer.android.com/guide/topics/fundamentals/… “(当活动停止时,它不再对用户可见,当其他地方需要内存时,它可以被系统杀死。” “如果活动被暂停或停止,则系统可以将其从内存中删除...通过要求它完成(调用其finish()方法)”。节省空间的活动。”
CommonsWare

42
在同一页面上,“但是,当系统破坏活动以恢复内存时”。您所指示的是Android不会破坏活动来回收内存,而只会终止这样做的过程。如果是这样,则此页面需要认真重写,因为它一再表明Android将销毁活动以回收内存。还要注意,许多引用的段落也存在于ActivityJavaDocs中。
CommonsWare

6
我以为我已经把它放在这里了,但是我发布了一个更新文档的要求:code.google.com/p/android/issues/detail?
id=21552

15
这是2013和文档只遇到更清楚地呈现假点:developer.android.com/training/basics/activity-lifecycle/...,“一旦你的活动停止后,如果需要恢复系统可能会破坏实例系统内存。在极端情况下,系统可能会简单地
终止

2
好吧,废话 我绝对一直认为(由于文档)系统会在您的流程中管理已停止的活动,并将根据需要对它们进行序列化和反序列化(销毁和创建)。
dcow

21

一些技巧:

  1. 确保您没有泄漏活动上下文。

  2. 确保您不在位图上保留引用。清理Activity#onStop中的所有ImageView,如下所示:

    Drawable d = imageView.getDrawable();  
    if (d != null) d.setCallback(null);  
    imageView.setImageDrawable(null);  
    imageView.setBackgroundDrawable(null);
    
  3. 如果不再需要位图,请对其进行回收。

  4. 如果您使用内存缓存(例如memory-lru),请确保它不占用太多内存。

  5. 不仅图像占用大量内存,而且请确保您在内存中不要保留过多的其他数据。如果您的应用中有无限多个列表,则很容易发生这种情况。尝试在数据库中缓存数据。

  6. 在Android 4.2上,存在一个具有硬件加速功能的bug(stackoverflow#13754876),因此,如果您hardwareAccelerated=true在清单中使用它,则会泄漏内存。GLES20DisplayList-即使您执行了步骤(2)并且没有其他人在引用此位图,也要保持引用。在这里您需要:

    a)禁用api 16/17的硬件加速;

    b)分离查看持有位图的视图

  7. 对于Android的3+,你可以尝试使用android:largeHeap="true"在你的AndroidManifest。但这并不能解决您的内存问题,只需推迟它们即可。

  8. 如果您需要无限导航,那么片段-应该是您的选择。因此,您将有1个活动,它将仅在片段之间切换。这样,您还将解决一些内存问题,例如数字4。

  9. 使用内存分析器找出导致内存泄漏的原因。
    这是来自Google I / O 2011的非常不错的视频:Android应用程序的内存管理
    如果要处理位图,则必须阅读以下内容:有效地显示位图


因此,如果我在过去20秒钟内打开了15个活动(用户正在经历非常快的速度),那可能是问题所在吗?在活动显示后,我应该添加哪一行代码来清除活动?我得到一个outOfMemory错误。谢谢!
Ruchir Baronia

4

在Android上,位图通常是导致内存错误的元凶,因此这将是一个很好的检查区域。


因此,如果我在过去20秒钟内打开了15个活动(用户正在经历非常快的速度),那可能是问题所在吗?在活动显示后,我应该添加哪一行代码来清除活动?我得到一个outOfMemory错误。谢谢!
Ruchir Baronia

2

您是否持有每个活动的参考?AFAIK这是阻止Android从堆栈中删除活动的原因。

您还可以在其他设备上重现此错误吗?根据ROM和/或硬件制造商,我遇到了一些Android设备的奇怪行为。


我能够在运行CM7的Droid上重现此内容,最大堆设置为16MB,这与我在模拟器上测试的值相同。
Artem Russakovskii

您可能正在尝试某些事情。当第二个活动开始时,第一个活动将执行onPause-> onStop还是仅执行onPause?因为我正在打印所有的********生命周期调用,并且看到的是onPause-> onCreate,而没有onStop。并且其中一个崩溃转储实际上针对被杀死的3个活动说了类似onPause = true或onStop = false之类的内容。
Artem Russakovskii

当活动离开屏幕时,应调用OnStop,但如果系统早日将其回收,则不能调用OnStop。
2011年

它没有被回收,因为如果单击返回,我看不到onCreate和onRestoreInstanceState被调用。
Artem Russakovskii

根据生命周期,这些可能永远都不会被调用,请查看我的回答,该链接有官方开发者博客的链接,很有可能是您传递位图的方式
dten

2

我认为问题可能是答案中提到的许多因素共同导致的。就像@Tim所说的那样,(静态)引用活动或活动中的元素会导致GC跳过活动。是讨论此方面的文章。我认为可能的问题来自使活动保持在“可见过程”状态或更高状态的某种东西,这将极大保证活动及其相关资源永远不会被回收。

一段时间以前,我通过Service遇到了相反的问题,这就是让我继续思考的原因:有些事情使您的Activity处于流程优先级列表的较高位置,这样它就不会受到系统GC的约束,例如参考(@Tim)或循环(@Alvaro)。循环不必是一个无尽的或长期运行的项目,而只是一个像递归方法或级联循环(或类似的循环)那样运行的东西。

编辑:据我了解,Android会根据需要自动调用onPause和onStop。这些方法主要是您可以覆盖的,以便您可以在停止托管过程之前照顾好自己需要做的事情(保存变量,手动保存状态等);但请注意,已明确声明可能并非在每种情况下都调用onStop(以及onDestroy)。此外,如果托管进程还托管状态为“ Forground”或“ Visible”的Activity,Service等,则OS甚至可能不会考虑停止进程/线程。例如:活动和服务都在同一过程中进行,服务START_STICKYonStartCommand()该过程至少会自动显示为可见状态。这可能是这里的关键,请尝试为Activity声明一个新的proc,然后看是否有任何改变。尝试将这行添加到清单中活动的声明中,如下:android:process=":proc2"如果活动与其他任何活动共享一个进程,则再次运行测试。这里的想法是,如果你已经清理你的活动,并相当肯定,问题是不是你的活动那么别的东西是问题,它的时间来猎人了点。

另外,我不记得在哪里看到过它(如果我什至在Android文档中也看到过它),但我还记得一些有关PendingIntent引用Activity的信息,可能会导致Activity以这种方式运行。

是该onStartCommand()页面的链接,其中包含有关非杀伤性流程方面的一些见解。


根据您提供的链接(谢谢),如果一个活动被调用了onStop,则该活动可以被杀死,但就我而言,我认为有些事情阻止了onStop的运行。我一定会调查原因。但是,它也说只有onPause的活动也可以被杀死,在我的情况下这是没有发生的(我看到onPause被调用了,但没有onStop)。
Artem Russakovskii

1

因此,我唯一真正想到的就是您是否有一个直接或间接引用上下文的静态变量。甚至还有一些东西可以作为对应用程序一部分的引用。我确定您已经尝试过了,但我会建议您以防万一,请尝试仅使onDestroy()中的所有静态变量为空,以确保垃圾收集器将其获取


1

我发现最大的内存泄漏源是由于对上下文的某些全局性,高级别或长期引用。如果将“上下文”存储在任何位置的变量中,则可能会遇到不可预测的内存泄漏。


是的,我听过很多遍,但我很难将其固定下来。如果我能可靠地弄清楚如何追踪它们的话。
Artem Russakovskii

1
在我的情况下,这转换为在类级别设置为等于上下文的任何任何变量(例如Class1.variable = getContext();)。通常,用新的对“ getContext”或类似内容的调用替换我应用程序中“上下文”的所有用法可以解决我最大的内存问题。但是我的是不稳定且不稳定的,就像您的情况一样不可预测,因此可能有所不同。
D2TheC 2011年

1

尝试将getApplicationContext()传递给任何需要Context的对象。您可能有一个全局变量,该变量持有对您的“活动”的引用,并防止对其进行垃圾回收。


1

在我的案例中,真正帮助解决内存问题的一件事最终是将我的位图的inPurgeable设置为true。请参阅为什么我永远不会使用BitmapFactory的inPurgeable选项?以及有关更多信息的答案讨论。

戴安娜·哈克伯恩(Dianne Hackborn)的回答以及我们随后的讨论(也感谢CommonsWare)有助于弄清我感到困惑的某些事情,因此谢谢您。


我知道这是一个老话题,但是我发现许多搜索似乎都找到了这个主题。我想链接一个很好的教程,我从中学到了很多,您可以在这里找到:developer.android.com/training/displaying-bitmaps/index.html
Codeversed 2013年

0

我也遇到了同样的问题。我正在开发一个即时消息传递应用程序,对于同一联系人,可以在ChatActivity中启动ProfileActivity,反之亦然。我只是在意图中添加了一个字符串以启动另一个活动,它接收了启动程序活动的类类型和用户ID的信息。例如,ProfileActivity启动一个ChatActivity,然后在ChatActivity.onCreate中,将调用者类类型标记为“ ProfileActivity”和用户ID,如果要启动一个Activity,我将检查该用户是否为“ ProfileActivity” 。如果是这样,只需调用“ finish()”并返回到以前的ProfileActivity,而不是创建一个新的ProfileActivity。内存泄漏是另一回事。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.