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

Max*_*org 22 android memory-leaks out-of-memory android-fragments

问题:

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

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

UserA -> ViewProfileFragment
         StoryViewFragment -> ViewProfileFragment
                               StoryViewFragment -> ViewProfileFragment
Run Code Online (Sandbox Code Playgroud)

因此,从典型的流程中可以看出,在Backstack中有很多实例ViewProfileFragmentStoryViewFragment堆积.

相关代码

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

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

我做了什么

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

2)我也删除了对addToBackStack()的调用,但显然这提供了糟糕的用户体验,因为它们无法遍历(当用户点击后退按钮时应用程序才会关闭).

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()
Run Code Online (Sandbox Code Playgroud)

质询

1)我是否忽略了一种方法,允许片段留在后台上,但也释放其资源,以便.replace(片段)的循环不会占用我所有的记忆?

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

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

Mar*_*ini 19

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

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

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

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

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

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

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

尽管如此,Fragments还是有效的.所以你的解决方案应该有效.

那么让我们开始看看谁/哪里可以保留这些硬参考.

注意:我只想在这里讨论如何调试这个,但我不太可能提供直接的解决方案.用它作为参考.

到底是怎么回事?:您正在向Backstack添加片段.backstack存储了对Fragment 的引用,而不是弱或软.(来源)

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

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

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

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

  1. 尝试使用像Picasso这样的简单图像加载器/缓存库,如果有的话可以确保图像不会泄漏.如果要使用自己的实现,可以稍后将其删除.尽管有这些缺陷,但毕加索的使用非常简单,并且已经进入一种以"正确的方式"处理记忆的状态.

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

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

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

总而言之,这些都是我的经验提出的建议,但除非我们能够详细了解,否则很难帮助您.

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

祝你好运.


rad*_*ual 7

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

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

因此,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();
    }
  }
}
Run Code Online (Sandbox Code Playgroud)

正如已经提到的那样,您正在堆叠物品在堆栈上,因此预计至少会有一点内存占用,但是当片段使用上述技术消失时,您可以通过清理资源来最小化占用空间.

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