Backstack上的Android碎片占用了太多的内存

问题:

我有一个Android应用程序,允许用户浏览到用户的configuration文件ViewProfileFragment 。 在ViewProfileFragment的内部,用户可以点击一个图像,将他带到StoryViewFragment各个用户的照片显示。 点击一个用户个人资料照片,可以将他们带到ViewProfileFragment另一个实例,并使用新的用户configuration文件。 如果用户重复点击用户的configuration文件,点击一个图像,将他们带到图库,然后点击另一个configuration文件碎片快速堆积在内存中导致可怕的OutOfMemoryError 。 以下是我所描述的stream程图:

UserA点击Bob的个人资料。 在Bob的个人资料里UserA点击ImageA,把他带到各种用户(包括Bob's)的照片库中。 UserA点击Sue的个人资料,然后点击其中一个图像 – 处理重复等。

 UserA -> ViewProfileFragment StoryViewFragment -> ViewProfileFragment StoryViewFragment -> ViewProfileFragment 

正如您从典型stream程中看到的那样, StoryViewFragmentStoryViewFragmentStoryViewFragment堆积了很多实例。

相关的代码

我用下面的逻辑将它们作为碎片加载:

 //from MainActivity fm = getSupportFragmentManager(); ft = fm.beginTransaction(); ft.replace(R.id.activity_main_content_fragment, fragment, title); ft.addToBackStack(title); 

我试过了什么

1)我特别使用FragmentTransaction replace以便在replace发生时触发onPause方法。 在onPause里面,我试图释放尽可能多的资源(比如清理ListView数据,清空variables等数据),这样当这个片段不是活动片段,并且被推到后台时,会有更多的记忆释放。 但是我释放资源的努力只是部分成功。 根据MAT,我仍然有很多被GalleryFragmentGalleryFragment使用的内存。

2)我也删除了对addToBackStack()的调用,但显然这提供了糟糕的用户体验,因为它们不能遍历(应用程序只是在用户点击后退button时closures)。

3)我用MAT来查找所有占用大量空间的对象,并且在onPause (和onResume )方法内部以各种方式处理这些对象以释放资源,但是它们的大小仍然相当大。

在这里输入图像说明

4)我也写了一个for循环在两个片段onPause使用以下逻辑将我的所有ImageViews为null:

  for (int i=shell.getHeaderViewCount(); i<shell.getCount(); i++) { View h = shell.getChildAt(i); ImageView v = (ImageView) h.findViewById(R.id.galleryImage); if (v != null) { v.setImageBitmap(null); } } myListViewAdapter.clear() 

质询

1)我是否忽视了一个让碎片留在背后的方法,同时也释放了它的资源,使得.replace(fragment)的循环不会把我所有的记忆都吃光了?

2)什么是“最佳实践”,当期望很多碎片可以装载到后台? 开发人员如何正确处理这种情况? (或者,我的应用程序中的逻辑固有缺陷,我只是做错了?)

任何帮助头脑风暴这个解决scheme将不胜感激。

Solutions Collecting From Web of "Backstack上的Android碎片占用了太多的内存"

事实certificate,片段与其父级活动共享相同的生命周期。 根据Fragment文档 :

片段必须始终embedded到活动中,片段的生命周期直接受到宿主活动生命周期的影响。 例如,当活动暂停时,其中的所有片段也是如此,当活动被破坏时,所有片段也是如此。 但是,当一个活动正在运行(处于恢复的生命周期状态)时,您可以独立操作每个片段。

因此,除非父活动暂停,否则您为清除片段的onPause()中的某些资源所采取的步骤将不会触发。 如果您有多个片段正在被父级活动加载,那么很可能您正在使用某种机制来切换哪个活动。

你可以通过不依赖onPause来解决你的问题,但是通过覆盖片段上的setUserVisibleHint 。 这为您提供了一个很好的地方,可以确定在片段进入和退出时(例如,当您有一个从FragmentA切换到FragmentB的PagerAdapter)时,在哪里进行资源设置或清理资源。

 public class MyFragment extends Fragment { @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); if (isVisibleToUser) { //you are visible to user now - so set whatever you need initResources(); } else { //you are no longer visible to the user so cleanup whatever you need cleanupResources(); } } } 

正如已经提到的那样,您将堆叠项目放在一个后台堆栈上,因此预计至less会有一点点内存占用,但是通过使用上述技术,在片段不在视图中时,可以通过清理资源来最小化占用空间。

另一个build议是要真正理解内存分析工具( MAT )的输出和一般的内存分析。 这是一个很好的起点 。 在Android中泄漏内存真的很容易,所以在我看来,熟悉这个概念以及内存如何摆脱你是非常必要的。 有可能你的问题是由于你不释放资源,当碎片离开视图时,以及某种内存泄漏,所以如果你使用setUserVisibleHint触发清理你的资源的路线,内存使用量,那么内存泄漏可能是罪魁祸首,所以一定要把它们统治出来。

很难看到整个图像(即使你已经向我们展示了大量的信息),没有具体的源代码访问,我相信如果不是不可能的话,这是不切实际的。

这就是说,使用碎片时,需要记住一些事情。 首先是一个免责声明。

当碎片被引入时,它们听起来像是所有时代中最好的想法。 能够同时显示多个活动 ,有点儿。 那是卖点。

所以整个世界慢慢开始使用碎片。 这是这个街区上的新人。 每个人都在使用碎片。 如果你不使用碎片,那么很可能是“你做错了”。

几年后,应用程序的趋势(幸好)恢复到更多的活动,更less的片段。 这是由新的API强制执行的(不需要用户真正注意的活动之间的转换能力,如Transition API等中所见)。

所以,总之: 我讨厌碎片 。 我相信这是有史以来最糟糕的Android实现之一,因为缺乏活动之间的Transition Framework(如今它的存在)而只获得了普及。 片段的生命周期(如果有的话)是一个随机callback球,当你期待它们时,永远不会保证被调用。

(好吧,我夸大了一点,但问任何Android经验丰富的开发者,如果他在某些时候对Fragments有问题,答案将是一个响亮的是)。

尽pipe如此,碎片的工作。 所以你的解决scheme应该工作。

那么让我们开始看看谁可以保留这些硬引用。

注意 :我只是想抛出这个想法,我将如何debugging,但我不可能提供一个直接的解决scheme。 用它作为参考。

到底是怎么回事? :你正在给Backstack添加碎片。 后台存储对片段的引用,而不是软弱。 ( 来源 )

现在谁存储一个后台? FragmentManager和…就像你猜测的那样,它也使用了一个现实的参考( 源代码 )。

最后,每个活动都包含对FragmentManager的一个硬引用。

简而言之:在你的活动消失之前,所有对其片段的引用都将存在于内存中。 无论在碎片pipe理器级别/ backstack中发生的添加/删除操作如何。

你能做什么? 我想起了一些事情。

  1. 尝试使用像毕加索这样简单的图像加载器/caching库,如果有的话确保图像不被泄漏。 如果你想使用你自己的实现,你可以稍后删除它。 毕加索的所有缺陷都非常简单易用,并且已经到了一个“正确的方式”。

  2. 从图片中删除“我可能泄漏的位图”问题(没有双关语!),那么现在是时候重温您的Fragment生命周期了。 当你把一个碎片放到栈中时,它不会被破坏,但是……你有机会清除资源:调用Fragment#onDestroyView() 。 这里是你想确保片段使任何资源无效的地方。

你没有提到你的片段是否使用了setRetainInstance(true) ,要小心,因为当Activity被销毁/重新创build(例如:旋转)时,这些不会被销毁/重新创build,如果处理不当,所有的视图都可能泄漏。

  1. 最后,但这很难诊断,也许你想重新审视你的架构。 您将多次启动相同的片段(viewprofile),您可能需要考虑重新使用同一个实例,并在其中加载“新用户”。 Backstack可以通过跟踪用户列表的顺序来处理,因此您可以拦截onBackPressed并将其向下移动,但始终在用户导航时加载新旧数据。 StoryViewFragment同样如此。

总而言之,这些都是从我的经验中得到的build议,但除非我们能够详细了解,否则很难帮助您。

希望它certificate是一个起点。

祝你好运。