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

时间:2015-02-12 17:10:49

标签: android android-fragments memory-leaks out-of-memory

问题:

我有一个Android应用程序,允许用户浏览用户的个人资料ViewProfileFragment。在ViewProfileFragment内,用户可以点击图片,将他带到StoryViewFragment,其中会显示各种用户的照片。可以单击用户个人资料照片,将其带到具有新用户个人资料的ViewProfileFragment的另一个实例。如果用户反复点击用户的个人资料,请点击将他们带到图库的图片,然后点击另一个配置文件,片段会快速堆叠在内存中,从而导致可怕的OutOfMemoryError。这是我所描述的图表流程图:

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

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

正如您从典型流程中可以看到的那样,在后台堆积中存在大量ViewProfileFragmentStoryViewFragment堆积的实例。

相关代码

我使用以下逻辑将它们作为片段加载:

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

我做了什么

1)我专门使用FragmentTransaction replace,以便在onPause发生时触发replace方法。在onPause内部我试图释放尽可能多的资源(例如清除ListView适配器中的数据,“清空”变量等),以便当片段不是活动片段时并且推到后台上会有更多的内存被释放。但我释放资源的努力只取得了部分成功。根据MAT,GalleryFragmentViewProfileFragment消耗的内存仍然很多。

2)我还删除了对addToBackStack()的调用,但很明显,这会导致用户体验不佳,因为他们无法遍历(当用户点击后退按钮时应用程序才会关闭)。

3)我使用MAT来查找占据了大量空间的所有对象,并且我已经在onPause(和onResume)方法中以各种方式处理这些对象以免费资源不足但规模仍然很大。

enter image description here

4)我还在两个片段'onPause中写了一个for循环,它使用以下逻辑将我的所有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(片段)的循环不会占用我所有的记忆?

2)当预计可以将大量碎片加载到靠背上时,“最佳实践”是什么?开发人员如何正确处理这种情况? (或者我的应用程序中的逻辑本质上是有缺陷的,我只是做错了吗?)

任何帮助集思广益的解决方案都会非常感激。

2 个答案:

答案 0 :(得分:19)

很难看到整个画面(即使你向我们展示了很多信息),但没有具体访问你的源代码,我相信如果不是不可能的话,这将是不切实际的。

话虽如此,在使用Fragments时还有一些事项要记住。首先是免责声明。

当片段被引入时,它们听起来像是有史以来最好的主意。能够同时显示多个活动,有点。这是卖点。

所以整个世界慢慢开始使用Fragments。这是街区的新生儿。每个人都在使用碎片。如果你没有使用片段,很可能是#34;你做错了#34;。

几年后和应用程序之后,这种趋势(幸运的是)回归到更多活动,更少碎片。这是由新的API强制执行的(在没有用户真正注意到的活动之间进行转换的能力,如在Transition API等中所见)。

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

(好吧,我夸大了一点,但问任何Android经验丰富的开发人员,如果他在某些时候遇到Fragments有问题,答案肯定是肯定的。)

尽管如此,片段仍有效。所以你的解决方案应该有效。

因此,让我们开始考虑谁/哪里可以保留这些硬参考。

注意:我只是想在此处讨论如何调试此问题,但我不太可能提供直接的解决方案。用它作为参考。

正在进行什么?: 您正在向Backstack添加片段。 backstack存储 hard 对Fragment的引用,而不是弱或软。 (source

现在谁存储了一个靠背? FragmentManager和......正如您所猜测的那样,它也使用了硬实时引用(source)。

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

简而言之:在您的活动消失之前,对其片段的所有引用都将存在于内存中。无论在Fragment Manager级别/ Backstack上发生的添加/删除操作如何。

你能做什么? 我想到了几件事。

  1. 尝试使用像Picasso这样的简单图像加载器/缓存库,以确保图像不会泄露。如果要使用自己的实现,可以稍后将其删除。尽管存在各种缺陷,但毕加索的使用非常简单,并且处于一种处理记忆状态的状态,并且正确的方式&#34;。

  2. 删除&#34后,我可能会泄漏位图&#34;问题出了问题(没有双关语!),那么现在是时候重新审视你的片段生命周期了。当你在后台堆放一个片段时,它没有被破坏,但是......你有机会清除资源:Fragment#onDestroyView()被调用。这里是您要确保片段无效任何资源的地方。

  3. 你没有提到你的片段是否正在使用setRetainInstance(true),要小心,因为当活动被销毁/重新创建时(例如:旋转),这些片段不会被销毁/重新创建,并且所有视图都可能被泄露如果处理不当。

    1. 最后,但这很难诊断,也许您想重新审视您的架构。您多次启动相同的片段(viewprofile),您可能需要考虑重复使用相同的实例并加载&#34;新用户&#34;在里面。可以通过按照加载顺序跟踪用户列表来处理Backstack,这样您就可以拦截onBackPressed并移动&#34; down&#34;堆栈,但总是在用户导航时加载新/旧数据。您的StoryViewFragment也是如此。
    2. 总而言之,这些都是我的经验提出的建议,但除非我们能够详细了解,否则很难帮助您。

      希望它被证明是一个起点。

      祝你好运。

答案 1 :(得分:7)

事实证明,片段与其父活动共享相同的生命周期。根据{{​​3}}:

  

片段必须始终嵌入到活动和片段中   生命周期直接受主机活动生命周期的影响。对于   例如,当活动暂停时,其中的所有片段也是如此,并且   当活动被破坏时,所有碎片都被破坏。然而,虽然   一个活动正在运行(它处于恢复的生命周期状态),你可以   独立操作每个片段。

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

您可以通过不依赖onPause而是通过覆盖片段上的Fragment documentation来解决您的问题。这为您提供了一个很好的位置,可以确定在片段进入和退出视图时(例如,当您有从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();
    }
  }
}

正如前面提到的那样,你正在堆叠后备箱上的物品,所以它预计至少会有一点内存占用,但你可以通过在片段出来时清理资源来最小化占用空间用上述技术观察。

另一个建议是要非常好地理解内存分析器工具(setUserVisibleHint)的输出和内存分析。 MAT。在Android中泄漏内存非常容易,因此在我看来,熟悉这个概念以及内存如何远离你是必要的。您的问题可能是因为当片段离开视图以及某种类型的内存泄漏时您没有释放资源,因此如果您使用{{1触发清理你的资源,你仍然看到大量的内存被使用,然后内存泄漏可能是罪魁祸首,所以一定要把它们都排除在外。