Backstack 上的 Android 片段占用太多内存 [英] Android Fragments on Backstack taking up too much memory

查看:31
本文介绍了Backstack 上的 Android 片段占用太多内存的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

问题:

我有一个 Android 应用程序,它允许用户浏览到用户的个人资料 ViewProfileFragment.在 ViewProfileFragment 中,用户可以单击图像,将他带到 StoryViewFragment,其中显示了各种用户的照片.可以单击用户个人资料照片,将他们带到带有新用户个人资料的 ViewProfileFragment 的另一个实例.如果用户反复单击用户的个人资料,然后单击将他们带到图库的图像,然后单击另一个个人资料,碎片会迅速堆积在内存中,从而导致可怕的 OutOfMemoryError.这是我所描述的流程图:

I have an Android application that allows a user to browse to a user's profile ViewProfileFragment. Inside ViewProfileFragment a user can click on an image that will take him to StoryViewFragment where various users' photos show up. It is possible to click on a user profile photo that will take them to another instance of ViewProfileFragment with the new user's profile. If a user repeatedly clicks on user's profiles, clicks an image that takes them to the gallery then clicks on another profile the Fragments stack up in memory quickly causing the dreaded OutOfMemoryError. Here is a diagram flow of what I am describing:

用户 A 点击鲍勃的个人资料.在 Bob 的个人资料中,用户 A 单击 ImageA 将他带到各种用户(包括 Bob 的)的照片库.用户 A 点击 Sue 的个人资料,然后点击她的一张图片 - 过程重复,等等.

UserA clicks on Bob's profile. Inside Bob's profile UserA clicks on ImageA taking him to a gallery of photos of various users (including Bob's). UserA clicks on profile of Sue then on one of her images - process repeats, etc, etc.

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

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

So as you can see from a typical flow there are lots of instances of ViewProfileFragment and StoryViewFragment piling up in the backstack.

相关代码

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

I am loading these in as fragments with the following logic:

//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 适配器中的数据、清空"变量等),以便当fragment 不是活动的 fragment 并被推到 backstack 会有更多的内存被释放.但是我释放资源的努力只是部分成功.根据 MA​​T,我仍然有很多内存被 GalleryFragmentViewProfileFragment 消耗.

1) I am specifically using FragmentTransaction replace so that the onPause method will be triggered when the replace takes place. Inside onPause I am trying to free up as many resources as I can (such as clearing out data in ListView adapters, "nulling" out variables, etc) so that when the fragment is not the active fragment and pushed onto the backstack there will be more memory freed up. But my efforts to free up resources is only a partial success. According to MAT I still have a lot of memory that is consumed by GalleryFragment and ViewProfileFragment.

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

2) I've also removed the call to addToBackStack() but obviously that offers a poor user experience because they can't traverse back (the app just closes when the user hits the back button).

3) 我已经使用 MAT 找到了所有占用大量空间的对象,并且我在 onPause(和 onResume) 方法来释放资源,但它们的大小仍然相当可观.

3) I have used MAT to find all of the objects that I take up a lot of space and I have dealt with those in various ways inside the onPause (and onResume) methods to free up resources but they are still considerable in size.

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

4) I also wrote a for loop in both fragments' onPause that sets all of my ImageViews to null using the following logic:

 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) 我是否忽略了一种允许 Fragment 保留在 backstack 但同时释放其资源以便 .replace(fragment) 的循环不会吃掉我所有内存的方法?

1) Am I overlooking a way to allow a Fragment to remain on the backstack but also free up its resources so that the cycle of .replace(fragment) doesn't eat up all of my memory?

2) 当预期可以将大量 Fragment 加载到 backstack 时,最佳实践"是什么?开发人员如何正确处理这种情况?(或者我的应用程序中的逻辑是否存在固有缺陷而我只是做错了?)

2) What are the "best practices" when it is expected that a lot of Fragments could be loaded onto the backstack? How does a developer correctly deal with this scenario? (Or is the logic in my application inherently flawed and I'm just doing it wrong?)

在头脑风暴解决方案方面的任何帮助将不胜感激.

Any help in brainstorming a solution to this would be greatly appreciated.

推荐答案

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

It turns out that fragments share the same lifecycle as their parent activity. According to the Fragment documentation:

片段必须始终嵌入到活动中,并且片段的生命周期直接受宿主活动生命周期的影响.为了例如,当活动暂停时,其中的所有片段也会暂停,并且当 Activity 被销毁时,所有的 Fragment 也会被销毁.然而,同时一个活动正在运行(它处于恢复生命周期状态),你可以独立操作每个片段.

A fragment must always be embedded in an activity and the fragment's lifecycle is directly affected by the host activity's lifecycle. For example, when the activity is paused, so are all fragments in it, and when the activity is destroyed, so are all fragments. However, while an activity is running (it is in the resumed lifecycle state), you can manipulate each fragment independently.

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

So the step that you took to clean up some resources in onPause() of the fragment wouldn't trigger unless the parent activity pauses. If you have multiple fragments that are being loaded by a parent activity then most likely you are using some kind of mechanism for switching which one is active.

您也许可以通过不依赖 onPause 而是通过覆盖 setUserVisibleHint 在片段上.当片段进入和离开视图时(例如,当您有一个从 FragmentA 切换到 FragmentB 的 PagerAdapter 时),这为您提供了一个确定在哪里设置资源或清理资源的好地方.

You might be able to solve your issue by not relying on the onPause but by overriding setUserVisibleHint on the fragment. This gives you a good place to determine where to do your setup of resources or clean up of resources when the fragment comes in and out of view (for example when you have a PagerAdapter that switches from FragmentA to FragmentB).

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();
    }
  }
}

正如已经提到的,您正在将项目堆叠在 backstack 上,因此预计至少会有一点内存占用,但是当片段不在视野中时,您可以通过清理资源来最小化占用以上技术.

As was already mentioned you are stacking items up on a backstack so it's expected that there will be at least a little bit of a memory footprint but you can minimize the footprint by cleaning up resources when the fragment is out of view with the above technique.

另一个建议是真正善于理解内存分析器工具的输出(MAT) 和一般的内存分析.这是一个很好的起点.在 Android 中泄漏内存真的很容易,所以我认为有必要熟悉这个概念以及内存如何远离你.您的问题可能是由于您在片段消失时没有释放资源以及某种内存泄漏,因此如果您使用 setUserVisibleHint 触发资源清理并且您仍然看到大量内存被使用,那么内存泄漏可能是罪魁祸首,因此请确保将它们都排除在外.

The other suggestion is to get really good at understanding the output of the memory analyzer tool (MAT) and memory analysis in general. Here is a good starting point. It is really easy to leak memory in Android so it's a necessity in my opinion to get familiar with the concept and how memory can get away from you. It's possible that your issues are due to you not releasing resources when the fragment goes out of view as well as a memory leak of some kind so if you go the route of using setUserVisibleHint to trigger cleanup of your resources and you still see a high-volume of memory being used then a memory leak could be the culprit so make sure to rule them both out.

这篇关于Backstack 上的 Android 片段占用太多内存的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆