-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Detect fragment view leaks #1061
Conversation
…agment's view after onDestroyView().
Thanks! Please read the contributing guidelines and sign the CLA. |
@pyricau, already did before submitting the PR. |
@igokoro did you fill in this form? https://spreadsheets.google.com/spreadsheet/viewform?formkey=dDViT2xzUHAwRkI3X3k5Z0lQM091OGc6MQ&ndplr=1 I'm not finding your GitHub username ( |
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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...
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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"> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nasty fragment is nasty.
(not necessarily requesting changes, just trying to get a better understanding) |
@pyricau, happy to answer your questions and update the code as needed. |
@pyricau, any updates? Is there anything that is holding up this PR? |
@pyricau this would be really cool as I've run into similar memory leaks. |
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
3ae7960
to
35ad391
Compare
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
Bitmap
s, 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.