Android 应用程序内存不足问题 - 尝试了所有方法但仍然不知所措
我花了整整 4 天的时间尝试一切可能的方法来解决我正在开发的应用程序中的内存泄漏问题,但事情很久以前就不再有意义了。
我正在开发的应用程序具有社交性质,因此请考虑配置活动 (P) 并列出包含数据的活动 - 例如徽章 (B)。您可以从个人资料跳到徽章列表,再跳到其他个人资料、其他列表等。
因此,想象一下像这样的流程 P1 -> B1-> P2-> B2-> P3-> B3 等。为了保持一致性,我加载同一用户的个人资料和徽章,因此每个 P 页面都是相同的,每个 B 页面也是如此。
问题的一般要旨是:导航一段时间后,根据每个页面的大小,我在随机位置(位图、字符串等)出现内存不足异常,它似乎不一致。
在尽一切努力找出内存不足的原因后,我什么也没得到。我不明白的是,为什么 Android 不会杀死 P1、B1 等,如果它在加载时内存不足,而是崩溃了。如果我通过 onCreate() 和 onRestoreInstanceState() 回到它们,我希望这些早期的活动会消失并复活。
更不用说这个了——即使我做P1——> B1->返回-> B1->返回-> B1,我还是崩溃了。这表明存在某种内存泄漏,但即使在转储 hprof 并使用 MAT 和 JProfiler 之后,我也无法查明它。
我已经禁用了从网络加载图像(并增加了加载的测试数据以弥补这一点并使测试公平),并确保图像缓存使用 SoftReferences。 Android 实际上会尝试释放它所拥有的少数 SoftReference,但就在内存不足崩溃之前。
徽章页面从网络获取数据,将其从 BaseAdapter 加载到 EntityData 数组中,并将其提供给 ListView(我实际上使用的是 CommonsWare 的 优秀的 MergeAdapter,但是在这个 Badge 活动中,实际上只有 1 个适配器,但无论如何我都想提一下这个事实)。
我已经检查了代码,但没有找到任何可能泄漏的内容。我清除并清空了我能找到的所有内容,甚至 System.gc() 左右,但应用程序仍然崩溃。
我仍然不明白为什么堆栈上的非活动活动没有被收获,我真的很想弄清楚这一点。
此时,我正在寻找任何提示,建议、解决方案……任何有帮助的事情。
谢谢。
I spent 4 full days trying everything I can to figure out the memory leak in an app I'm developing, but things stopped making sense a long time ago.
The app I'm developing is of social nature, so think profile Activities (P) and list Activities with data - for example badges (B). You can hop from profile to a badge list to other profiles, to other lists, etc.
So imagine a flow like this P1 -> B1 -> P2 -> B2 -> P3 -> B3, etc. For consistency, I'm loading profiles and badges of the same user, so each P page is the same and so is each B page.
The general gist of the problem is: after navigating for a bit, depending on the size of each page, I get an out-of-memory exception in random places - Bitmaps, Strings, etc - it doesn't seem to be consistent.
After doing everything imaginable to figure out why I am running out of memory, I have come up with nothing. What I don't understand is why Android isn't killing P1, B1, etc if it runs out of memory upon loading and instead crashes. I would expect these earlier activities to die and be resurrected if I ever Back to them via onCreate() and onRestoreInstanceState().
Let alone this - even if I do P1 -> B1 -> Back -> B1 -> Back -> B1, I still get a crash. This indicates some sort of a memory leak, yet even after dumping hprof and using MAT and JProfiler, I can't pinpoint it.
I've disabled loading of images from the web (and increased the test data loaded to make up for it and make the test fair) and made sure the image cache uses SoftReferences. Android actually tries to free up the few SoftReferences it has, but right before it crashes out of memory.
Badge pages get data from the web, load it into an array of EntityData from a BaseAdapter and feed it to a ListView (I'm actually using CommonsWare's excellent MergeAdapter, but in this Badge activity, there is really only 1 adapter anyway, but I wanted to mention this fact either way).
I've gone through the code and was not able to find anything that would leak. I cleared and nulled everything I could find and even System.gc() left and right but still the app crashes.
I still don't understand why inactive activities that are on the stack don't get reaped, and I'd really love to figure that out.
At this point, I'm looking for any hints, advice, solutions... anything that could help.
Thank you.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(10)
事情不是这样的。影响 Activity 生命周期的唯一内存管理是所有进程中的全局内存,因为 Android 认为其内存不足,因此需要终止后台进程以恢复内存。
如果您的应用程序位于前台启动越来越多的活动,则它永远不会进入后台,因此在系统接近终止其进程之前,它总是会达到其本地进程内存限制。 (当它确实终止其进程时,它将终止托管所有活动的进程,包括当前位于前台的任何内容。)
所以在我看来,你的基本问题是:你也让许多活动同时运行,和/或其中每项活动都占用了太多资源。
您只需要重新设计导航,而不是依赖于堆叠任意数量的潜在重量级活动。除非您在 onStop() 中执行大量操作(例如调用 setContentView() 来清除 Activity 的视图层次结构并清除它可能保留的其他内容的变量),否则您将耗尽内存。
您可能需要考虑使用新的 Fragment API 将任意活动堆栈替换为更严格管理内存的单个活动。例如,如果您使用片段的返回堆栈功能,当片段进入返回堆栈并且不再可见时,将调用其 onDestroyView() 方法以完全删除其视图层次结构,从而大大减少其占用空间。
现在,只要你在流程中崩溃,你按下回车键,转到一个活动,按下回车键,转到另一个活动等等,并且永远不会有深堆栈,那么是的,你只是有泄漏。这篇博文介绍了如何调试泄漏: http:// android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
This is not how things work. The only memory management that impacts activity lifecycle is the global memory across all processes, as Android decides that it is running low on memory and so need to kill background processes to get some back.
If your application is sitting in the foreground starting more and more activities, it is never going into the background, so it will always hit its local process memory limit before the system ever comes close to killing its process. (And when it does kill its process, it will kill the process hosting all the activities, including whatever is currently in the foreground.)
So it sounds to me like your basic problem is: you are letting too many activities run at the same time, and/or each of those activities is holding on to too many resources.
You just need to redesign your navigation to not rely on stacking up an arbitrary number of potentially heavy-weight activities. Unless you do a serious amount of stuff in onStop() (such as calling setContentView() to clear out the activity's view hierarchy and clear variables of whatever else it may be holding on to), you are just going to run out of memory.
You may want to consider using the new Fragment APIs to replace this arbitrary stack of activities with a single activity that more tightly manages its memory. For example if you use the back stack facilities of fragments, when a fragment goes on the back stack and is no longer visible, its onDestroyView() method is called to completely remove its view hierarchy, greatly reducing its footprint.
Now, as far as you crashing in the flow where you press back, go to an activity, press back, go to another activity, etc and never have a deep stack, then yes you just have a leak. This blog post describes how to debug leaks: http://android-developers.blogspot.com/2011/03/memory-analysis-for-android.html
一些提示:
确保您没有泄漏活动上下文。
确保您没有在位图上保留引用。清理 Activity#onStop 中的所有 ImageView,如下所示:
如果不再需要位图,则回收它们。
如果您使用内存缓存,例如内存-lru,请确保它没有使用太多内存。
不仅图像占用大量内存,请确保内存中不要保存太多其他数据。如果您的应用程序中有无限列表,则很容易发生这种情况。尝试在数据库中缓存数据。
在 android 4.2 上,有一个 bug(stackoverflow#13754876)使用硬件加速,因此如果您在清单中使用
hardwareAccelerated=true
,则会泄漏内存。GLES20DisplayList
- 保持引用,即使您执行了步骤 (2) 并且没有其他人引用此位图。这里你需要:a) 禁用 api 16/17 的硬件加速;
或
b) 分离持有位图的视图
对于 Android 3+,您可以尝试在
AndroidManifest
中使用android:largeHeap="true"
。但这并不能解决你的内存问题,只是推迟它们。如果您需要无限导航,那么 Fragments - 应该是您的选择。所以你将有 1 个活动,它只会在片段之间切换。这样您还可以解决一些内存问题,例如数字 4。
使用内存分析器找出内存泄漏的原因。
这是Google I/O 2011:Android 应用的内存管理
如果您处理位图,这应该是必读的:高效显示位图
Some tips:
Make sure you are not leak activity context.
Make sure you are don't keep references on bitmaps. Clean all of your ImageView's in Activity#onStop, something like this:
Recycle bitmaps if you don't need them anymore.
If you use memory cache, like memory-lru, make sure it is not using to much memory.
Not only images take alot of memory, make sure you don't keep too much other data in memory. This easily can happens if you have infinite lists in your app. Try to cache data in DataBase.
On android 4.2, there is a bug(stackoverflow#13754876) with hardware acceleration, so if you use
hardwareAccelerated=true
in your manifest it will leak memory.GLES20DisplayList
- keep holding references, even if you did step (2) and no one else is referencing to this bitmap. Here you need:a) disable hardware acceleration for api 16/17;
or
b) detach view that holding bitmap
For Android 3+ you can try to use
android:largeHeap="true"
in yourAndroidManifest
. But it will not solve your memory problems, just postpone them.If you need, like, infinite navigation, then Fragments - should be your choice. So you will have 1 activity, which will just switch between fragments. This way you will also solve some memory issues, like number 4.
Use Memory Analyzer to find out the cause of your memory leak.
Here is very good video from Google I/O 2011: Memory management for Android Apps
If you dealing with bitmaps this should be a must read: Displaying Bitmaps Efficiently
位图通常是 Android 上内存错误的罪魁祸首,因此这是一个需要仔细检查的好地方。
Bitmaps are often the culprit for memory errors on Android, so that would be a good area to double check.
您是否持有每项活动的一些参考资料?据我所知,这是阻止 Android 从堆栈中删除活动的原因。
您是否也能够在其他设备上重现此错误?我遇到过一些 Android 设备的一些奇怪行为,具体取决于 ROM 和/或硬件制造商。
Are you holding some references to each Activity? AFAIK this is a reason which keeps Android from deleting activities from the stack.
We're you able to reproduce this error on other devices as well? I've experienced some strange behaviour of some android devices depending on the ROM and/or hardware manufacturer.
我认为这个问题可能是答案中所述的许多因素的组合,这就是给您带来问题的原因。就像 @Tim 所说,对活动或该活动中的元素的(静态)引用可能会导致 GC 跳过该活动。 这里是讨论此方面的文章。我认为可能的问题来自于将活动保持在“可见进程”状态或更高状态,这几乎可以保证活动及其相关资源永远不会被回收。
不久前我在服务中遇到了相反的问题,所以这就是让我继续这个想法的原因:有一些东西可以让你的 Activity 在进程优先级列表中保持较高的位置,这样它就不会受到系统 GC 的影响,例如引用(@Tim)或循环(@Alvaro)。循环不需要是无限的或长时间运行的项目,只需像递归方法或级联循环(或类似的东西)一样运行。
编辑: 据我了解,onPause 和 onStop 会根据需要由 Android 自动调用。这些方法主要供您覆盖,以便您可以在托管进程停止之前处理您需要做的事情(保存变量、手动保存状态等);但请注意,明确指出 onStop(与 onDestroy 一起)可能不会在每种情况下都被调用。此外,如果托管进程还托管具有“Forground”或“Visible”状态的活动、服务等,则操作系统甚至可能不会考虑停止进程/线程。例如:一个 Activity 和一个 Service 都在同一个进程中,并且 Service 从
onStartCommand()
返回START_STICKY
,进程会自动采取至少一个可见状态。这可能是这里的关键,尝试为 Activity 声明一个新的过程,看看是否会改变任何东西。尝试将此行添加到清单中的活动声明中,如下所示:android:process=":proc2"
,然后如果您的活动与其他任何活动共享进程,则再次运行测试。这里的想法是,如果您已经清理了您的 Activity 并且非常确定问题不是您的 Activity 造成的,那么问题就出在其他地方,那么就该寻找这个问题了。另外,我不记得在哪里看到过它(如果我什至在 Android 文档中看到过它),但我记得有关引用 Activity 的
PendingIntent
可能会导致 Activity 出现这种行为。此处 是
onStartCommand()
页面的链接,其中包含有关进程非终止前端的一些见解。I think the problem maybe a combination of many factors stated here in the answers are what is giving you problems. Like @Tim said, a (static) reference to an activity or an element in that activity can cause the GC to skip the Activity. Here is the article discussing this facet. I would think the likely issue comes from something keeping the Activity in an "Visible Process" state or higher, which will pretty much guaranty that the Activity and its associated resources never get reclaimed.
I went through the opposite problem a while back with a Service, so that's what got me going on this thought: there is something keeping your Activity high on the process priority list so that it won't be subject to the system GC, such as a reference (@Tim) or a loop (@Alvaro). The loop doesn't need to be an endless or long running item, just something that runs a lot like a recursive method or cascaded loop (or something along those lines).
EDIT: As I understand this, onPause and onStop are called as needed automatically by Android. The methods are there mainly for you to overide so that you can take care of what you need to before the hosting process is stopped (saving variables, manually saving state, etc.); but note that it is clearly stated that onStop (along with onDestroy) may not be called in every case. Additionally, if the hosting process is also hosting an Activity, Service, etc. that has a "Forground" or "Visible" status, the OS might not even look at stopping the process/thread. For example: an Activity and a Service are both luanched in the same process and the Service returns
START_STICKY
fromonStartCommand()
the process automatically takes at least a visible status. That might be the key here, try declaring a new proc for the Activity and see if that changes anything. Try adding this line to the declaration of your Activity in the Manifest as:android:process=":proc2"
and then run the tests again if your Activity shares a process with anything else. The thought here is that if you've cleaned up your Activity and are pretty sure that the problem is not your Activity then something else is the problem and its time to hunter for that.Also, I can't remember where I saw it (if I even saw it in the Android docs) but I remember something about a
PendingIntent
referencing an Activity may cause an Activity to behave this way.Here is a link for the
onStartCommand()
page with some insights on the process non-killing front.在我的案例中,真正解决内存问题的方法之一最终是将我的位图的 inPurgeable 设置为 true。请参阅为什么我不使用 BitmapFactory 的 inPurgeable 选项? 以及答案的讨论以获取更多信息。
Dianne Hackborn 的回答和我们随后的讨论(也感谢 CommonsWare)帮助澄清了我感到困惑的某些事情,所以谢谢你。
One of the things that really helped the memory issue in my case ended up being setting inPurgeable to true for my Bitmaps. See Why would I ever NOT use BitmapFactory's inPurgeable option? and the answer's discussion for more info.
Dianne Hackborn's answer and our subsequent discussion (also thanks, CommonsWare) helped clarify certain things I was confused about, so thank you for that.
所以我唯一能想到的是如果你有一个直接或间接引用上下文的静态变量。甚至是对应用程序部分内容的引用。我确信您已经尝试过,但我建议您以防万一,尝试将 onDestroy() 中的所有静态变量清空,以确保垃圾收集器得到它
so the only thing i can really think of is if you have a static variable that references directly or indirectly to the context. Even something so much as a reference to part of the application. I'm sure you have already tried it but i will suggest it just in case, try just nulling out ALL of your static variables in the onDestroy() just to make sure the garbage collector gets it
我发现内存泄漏的最大来源是由对上下文的某些全局、高级或长期引用引起的。如果将“上下文”存储在任何地方的变量中,则可能会遇到不可预测的内存泄漏。
The biggest source of memory leak I have found was caused by some global, high level or long-standing reference to the context. If you are keeping "context" stored in a variable anywhere, you may encounter unpredictable memory leaks.
尝试将 getApplicationContext() 传递给任何需要上下文的东西。您可能有一个全局变量,它保存对您的活动的引用并防止它们被垃圾收集。
Try passing getApplicationContext() to anything that needs a Context. You might have a global variable that is holding a reference to your Activities and preventing them from being garbage collected.
我和你遇到了同样的问题。我正在开发一个即时通讯应用程序,对于同一个联系人,可以在 ChatActivity 中启动 ProfileActivity,反之亦然。
我只是在意图中添加一个额外的字符串来启动另一个活动,它需要启动活动的类类型信息和用户 ID。例如,ProfileActivity启动一个ChatActivity,然后在ChatActivity.onCreate中,我标记调用者类类型'ProfileActivity'和用户id,如果它要启动一个Activity,我会检查它是否是用户的'ProfileActivity' 。如果是这样,只需调用“finish()”并返回到前一个 ProfileActivity,而不是创建一个新的。
内存泄漏是另一回事。
I encountered the same problem with you. I was working on a instant messaging app, for the same contact, it is possible to start a ProfileActivity in a ChatActivity, and vice versa.
I just add a string extra into the intent to start another activity, it takes the information of class type of starter activity, and the user id. For example, ProfileActivity starts a ChatActivity, then in ChatActivity.onCreate, I mark the invoker class type 'ProfileActivity' and user id, if it's going to start an Activity, I would check whether it is a 'ProfileActivity' for the user or not. If so, just call 'finish()' and go back to the former ProfileActivity instead of creating a new one.
Memory leak is another thing.