Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Detect fragment view leaks #1061

Merged
merged 2 commits into from
Oct 16, 2018

Conversation

igokoro
Copy link
Contributor

@igokoro igokoro commented Jul 23, 2018

Extension of the fragment memory leak detection to also keep an eye on fragment's view after onDestroyView(). This is important for cases when a fragment is replaced and added to the backstack - it does not possess a view but is also not destroyed.

If a fragment's view includes any memory-heavy resources, like Bitmaps, a memory leak may become rather severe after a couple of such fragments are added to the backstack. In such cases, it's important to allow GC to do its job and release memory from all "destroyed" views so that new fragments can be displayed.

This PR automates the setup of a fragment's view leak detection.

@pyricau
Copy link
Member

pyricau commented Jul 24, 2018

Thanks!

Please read the contributing guidelines and sign the CLA.

@igokoro
Copy link
Contributor Author

igokoro commented Jul 24, 2018

@pyricau, already did before submitting the PR.

@pyricau
Copy link
Member

pyricau commented Jul 25, 2018

@igokoro did you fill in this form? https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1

I'm not finding your GitHub username (@igokoro) in the sheet.

@igokoro
Copy link
Contributor Author

igokoro commented Jul 25, 2018

@pyricau, yes that form - it's also referenced from the contributing guidelines page. I think I used "igokoro" as GitHub username. But anyway - just filled the form again, this time with "@igokoro".

@pyricau
Copy link
Member

pyricau commented Jul 25, 2018

Sorry about that, I just found you twice... I guess I didn't search right. Thanks!

public ActivityTestRule<TestActivity> activityRule = new ActivityTestRule<>(
TestActivity.class);
public ActivityTestRule<FragmentActivity> activityRule =
new ActivityTestRule<>(FragmentActivity.class, true, false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this need to change, and does that relate to adding activityRule.launchActivity in the test below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct - it does. Instead of introducing a new Test class - I've updated the existing one to serve the needs of the new test case. With such setup, each test case in this class can start its own activity. This can be reverted if you prefer a separate test class for the new test.

activityRule.launchActivity(new Intent(InstrumentationRegistry.getTargetContext(), NastyActivity.class));

Espresso.onView(ViewMatchers.withId(R.id.add)).perform(click());
Espresso.onView(ViewMatchers.withId(R.id.add)).perform(click());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the test adding the fragment twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a specific setup for fragment's view leak scenario. Imagine the situation: social network app, from your profile page you open your friend's page, then from his page - his friend's friend and so on - virtually infinite stack.
Here, if replace + addToBackStack fragment transaction is executed - the replaced fragment will not be "destroyed", only onDestroyView() will be executed and only the view should be gone from memory.
So, adding the same fragment twice is just the simplest way to reproduce the problem. Technically, we can add any other fragment on top, not necessarily the same - but that is one more class to create for no particular reason...

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could you please explain why now after version 1.6.2 I got leak on replace + addToBackStack operations, and how to handle it?


import com.squareup.leakcanary.support.fragment.R;

public class NastyActivity extends FragmentActivity {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Couldn't we add the add view to TestActivity instead?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, that was my first idea. But then I decided to keep separate test scenarios separated. I do not necessarily understand all the details about the original test case, so didn't want to break it.
Besides, such changes are easier to understand in isolation. Combined together in a single activity, the new test case would need to ignore the static fragment leak from the original test.


@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
label = view.findViewById(R.id.label);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's interesting, I feel like this must be a common mistake. Do you know of Google documentation recommending to not do that or to clear view references in onDestroyView?

Is this something that one should do on all fragments, or just for some special cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I do agree - it has to be a common mistake. That's why I decided to submit this PR :)
Honestly, I'm pretty sure I was doing things like this for years. I'm not aware of any documentation that highlights the issues - I didn't check recently though.

I know that such memory leak is possible when replace + addToBackStack fragment transaction is executed. So, given that replace is add + remove - the same issues can happen for any removed fragments that are added to back stack.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also occurs when you use detach regardless of the backstack.

android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:text="I'm real nasty">
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nasty fragment is nasty.

@pyricau
Copy link
Member

pyricau commented Jul 26, 2018

(not necessarily requesting changes, just trying to get a better understanding)

@igokoro
Copy link
Contributor Author

igokoro commented Jul 27, 2018

@pyricau, happy to answer your questions and update the code as needed.

@igokoro
Copy link
Contributor Author

igokoro commented Aug 31, 2018

@pyricau, any updates? Is there anything that is holding up this PR?

@SUPERCILEX
Copy link

@pyricau this would be really cool as I've run into similar memory leaks.

@pyricau
Copy link
Member

pyricau commented Oct 16, 2018

Note: CLA signed. I'd like to follow up with a few changes, can you do this? => https://help.github.com/articles/allowing-changes-to-a-pull-request-branch-created-from-a-fork/ It should enable me to add commits on top of the PR.

- Factoring test utility code out
- Adding latches to avoid flakes
- Removing "nasty", using descriptive names
@pyricau pyricau force-pushed the feature/detect_fragment_view_leaks branch from 3ae7960 to 35ad391 Compare October 16, 2018 23:19
@pyricau pyricau merged commit 7d7d2d0 into square:master Oct 16, 2018
@pyricau pyricau added this to the Next release milestone Oct 16, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants