Memory leak in FragmentManager - android

Update:
I think the leak is coming from getActivity().getSupportLoaderManager().restartLoader(getLoaderId(), null, this);
where i have my object implement LoaderCallback. Is there a way for me to clear the callback i tired setting it to
getActivity().getSupportLoaderManager().restartLoader(getLoaderId(), null, null);
but this crashes
Orig:
I have a list of objects in one of my fragments(A). When I navigate forward I add fragment A to the backstack. After I have navigated to a new fragment and I dump the heap. I still see my object in the heap. When I get the shortest path in the dump it looks like below. I can see that in FragmentManagerImpl there is a reference to fragment A in mActive fragments which is keeping my lists object alive.
Is my fragment supposed to stay in mActive fragments or is this a leak?
Adding to backstack
FragmentTransaction transaction = mFragmentManager.beginTransaction();
updateTransactionWith(info.getReplacement(), transaction, "replace");
transaction.addToBackStack(info.getReplacement().getClass().toString());
transaction.commit();
mFragmentManager.executePendingTransactions();

By calling addToBackStack(), you're requesting the FragmentManager that the Fragment being replaced be just stopped and not destroyed because you're either anticipating that a back button press is very likely or, the Fragment is heavy on initialization and you would still like to avoid doing it again even though the user is not very likely to go back.
The docs clearly state that
If you do not call addToBackStack() when you perform a transaction
that removes a fragment, then that fragment is destroyed when the
transaction is committed and the user cannot navigate back to it.
Whereas, if you do call addToBackStack() when removing a fragment,
then the fragment is stopped and will be resumed if the user navigates
back.
Hence, it's not a memory leak and your observations are quite in line with the expected behaviour.
However, just like an Activity, the system may still choose to destroy this Fragment, if it's running out of memory. But, that's expected behaviour too.

It's not a memory leak. You need to decide how to deal with your fragment's state.
Ideally you implement onSaveInstanceState and onViewStateRestored saving your state to the bundle and restoring it from the bundle respectively.
Alternatively, if you're able to re-create your state easily, you may want to save the bother of (re)storing it using the bundle and just null your references in the onPause method and create them during the onResume method. Be aware that onResume gets called even if the fragment has just been created, so be careful not to do that work more than once.
Either way, be sure to null your references to ensure your objects are marked for GC.
The FragmentManager will decide if it needs to discard and recreate the fragment as necessary in order to allow the user to go back to the fragment you added to the stack. In conditions where there's very little else on the stack and/or there's lots of spare memory it will probably just keep a direct reference to the fragment you added to the back stack.
Given all that, you also need to be careful about keeping references to other fragments, activities, etc as that kind of state is difficult to recreate.
The following approach is recommended for providing proper back navigation:
// Works with either the framework FragmentManager or the
// support package FragmentManager (getSupportFragmentManager).
getSupportFragmentManager().beginTransaction()
.add(detailFragment, "detail")
// Add this transaction to the back stack
.addToBackStack()
.commit();
More info:
http://developer.android.com/guide/components/fragments.html
http://developer.android.com/training/implementing-navigation/temporal.html
API docs: http://developer.android.com/reference/android/app/Fragment.html

Related

How to clear backStack of support FragmentManager?

Is there any way, how to clear backStack of support FragmentManager without calling onCreateView() in stored fragments?
I understand fragment lyfe cycle and calling onDestroyView() and onCreateView() when it is popped.
http://developer.android.com/guide/components/fragments.html#Creating
Also I know how to pop all fragments from backstack with
mFragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
or
for(int i = 0; i < mFragmentManager.getBackStackEntryCount(); ++i) {
mFragmentManager.popBackStack();
}
but both ways are calling onCreateView() and other lyfe cycle methods until to onDestroyView() and onDestroy().
But is there any way, how to clear this backstack with calling only from onDestroyView() and not from onCreateView() (inside of fragments)?
Or is there any way how to do replace transaction with clearing previous fragments?
For example, I want clear backstact before I do transaction:
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE);
fragmentManager.beginTransaction().replace(R.id.content, fragment).commit();
I haven't found a simple solution to this issue. I'm almost certain there is no feature of FragmentManager or Fragment that allows you to control which lifecycle method are called when a fragment is popped from the stack. I'll outline two possible approaches. Each has some undesirable aspects.
The first approach assumes the fragments you are popping out of the backstack are children of an activity. If they are children of a fragment, the method still applies, just a different type of parent object.
Add a boolean member mClearingBackStack to the activity (or parent fragment) with a getter method. Set the boolean only when you are starting a complete clear of the backstack. In the fragment lifecycle methods where you want to disable processing, get the flag and modify the processing accordingly. For onCreateView() through onDestroyView(), the fragment will be attached and the host activity available with getActivity(). Cast it to whatever your activity class is to use the flag's getter method.
Because popBackStack() is asynchronous, clearing the flag must be done only after the stack unwinding completes. I haven't tried it, but I think posting a Runnable after calling popBackStack() to clear the flag should work. Because the Runnable needs to go at the end of the queue, View.post() must be used instead of Activity.runOnUiThread(). An alternative is to call executePendingTransactions() to wait for the stack unwinding to complete.
The second approach is cleaner, if your design can accommodate it. Create a place-holder fragment that is a child of your activity and parent to all your other fragments. For all the fragment transactions you have now, use the new fragment's FragmentManager, obtained using getChildFragmentManager(). When you want to clear all those transactions, instead of popping the child fragment manager's stack, remove or replace the parent fragment in the activity's fragment manager. When the parent fragment is removed, all of its children are destroyed and go through the teardown steps, onDestroyView(), onDestroy(), etc. but not all the steps that would occur if its backstack were unwound. This approach is much simpler and more maintainable than the first, but requires you to rework your fragment hierarchy. An additional problem with this approach is that you must add some code to handle the Back action with a fragment hierarchy. The problem and various solutions are described here.

Android - Fragment replace not replaced

I want to replace fragment and destroy previous.
Here my code :
FragmentManager manager = getFragmentManager();
FragmentTransaction newT = manager.beginTransaction();
newT.replace(R.id.activity_content currentFragment, CE_TAG);
newT.commit();
I read this article : http://sapandiwakar.in/replacing-fragments/
I've not put the fragment in back stack, but my fragement are not destroyed !
onPause(), onDestroy(), onStop() are never called.
Thanks.
Try
Fragment f = new YOurFragment();
fragmentTransaction.remove(f).commit();
Hope it helps
A fragment is maintained in memory even after it is no longer visible. If you call it back into existence, onCreateView is called and the visual part of the fragment is recreated but the actual object is reused. From the Docs:
onDestroy() = The final call you receive before your activity is
destroyed. This can happen either because the activity is finishing
(someone called finish() on it, or because the system is temporarily
destroying this instance of the activity to save space. You can
distinguish between these two scenarios with the isFinishing() method.
You should consider onDestroyView instead, or call finish if that suits you better.
http://developer.android.com/reference/android/app/Fragment.html
Try adding this line of code after committing the transaction.
getChildFragmentManager().executePendingTransactions();
Check this executePendingTransactions.
If you are replacing a fragment with the same tag, it may cause the issue of overlaping. Also, if your fragment container is a LinearLayout, it cannot update after replace (from my experience only).

Best way to switch between two fragments

I'm interested in the best way to have a single activity that switches between two fragments.
I've read probably 15 Stack Overflow posts and 5 blogs posts on how to do this, and, while I think I cobbled together a solution, I'm not convinced it's the best one. So, I want to hear people's opinions on the right way to handle this, especially with regards to the lifecycle of the parent activity and the fragments.
Here is the situation in detail:
A parent activity that can display one of two possible fragments.
The two fragments have state that I would like to persist across a session, but does not necessarily need to be persisted between sessions.
A number of other activities, such that the parent activity and the fragments could get buried in the back stack and destroyed due to low memory.
I want the ability to use the back button to move between the fragments (So as I understand it, I can't use setRetainInstance).
In addition to general architecture advice, I have the following outstanding questions:
If the parent activity is destroyed due to low memory, how do I guarantee that the states of both fragments will be retained, as per this post: When a Fragment is replaced and put in the back stack (or removed) does it stay in memory?. Do I just need a pointer to each fragment in the parent activity?
What is the best way for the parent activity to keep track of which fragment it is currently displaying?
Thanks in advance!
I ended up adding both of the fragments using the support fragment manager and then using detach/attach to switch between them. I was able to use commitAllowingStateLoss() because I retain the state of the view elsewhere, and manually set the correct fragment in onResume().
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.add(R.id.my_layout, new AFragment(), TAG_A);
fragmentTransaction.add(R.id.my_layout, new BFragment(), TAG_B);
fragmentTransaction.commit();
}
public void onResume() {
super.onResume();
if (this.shouldShowA) {
switchToA();
} else {
switchToB();
}
}
private void switchToA() {
AFragment fragA = (AFragment) getSupportFragmentManager().findFragmentByTag(TAG_A);
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.detach(getSupportFragmentManager().findFragmentByTag(TAG_B));
fragmentTransaction.attach(fragA);
fragmentTransaction.addToBackStack(null);
fragmentTransaction.commitAllowingStateLoss();
getSupportFragmentManager().executePendingTransactions();
}
You might want to consider using a ViewPager in your parent Activity so you can switch between the Fragments.
So you would be able to swipe through them.
if you want to persist their state during a session even if the parent activity is destroyed, you need to make them Parcelable, so you can save the state even if the class isn't instantiated at that time. You also need to do this if your rotating your device and want to keep the current situation/data on the Screen.
You should write them to a Parcelable in their onPause methods and recreate them from it in the onResume one. This way it doesn't matter if they are destroyed or have to be recreated due to changes in the devices orientation.
if you want to be able to switch between those fragments with the Backbutton, you can catch the buttonClick for onBackPressed and handle it accordingly.
If you need to figure out what Fragment your displaying at a given time you ask your ViewPager what Fragment he is displaying at that time, so you don't have to keep track, you can just ask someone who knows, if you need to know it.

Android Fragment View State Loss When Using FragmentTransaction.replace()

I am having a pretty big issue and I am not quite understanding what is happening. I am developing an application that uses Fragments (from the support library) and am using FragmentTransaction.replace() to place new Fragments on to the back stack and replace the old one. The code looks as follows:
FragmentManager fm = getSupportFragmentManager();
FragmentTransaction ft = ft.beginTransaction();
// Animations in my res/anim folder
ft.setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left, R.anim.slide_in_left, R.anim.slide_out_right);
ft.replace(R.id.fragment_container, newFragment, tag);
ft.addToBackStack(null);
ft.commit();
This is successful in replacing my fragment. My issue is the following. In one Fragment, I have a list of items that is built from user input. Now, when the user clicks next and then clicks the back button (to return to the list), the list is empty because the view is destroyed. Now, I have noted the following:
onSaveInstanceState is not called. I believe this is because that is only called when the parent Activity tells it to. Based on the docs: " There are many situations where a fragment may be mostly torn down (such as when placed on the back stack with no UI showing), but its state will not be saved until its owning activity actually needs to save its state.". Apparently, performing a replace on the FragmentTransaction is not one of those times. Does anyone have confirmation on this or a better explanation?
setOnRetainInstanceState(true) is not helpful in this situation. Again, I believe this has to do with info from the docs: "Control whether a fragment instance is retained across Activity re-creation (such as from a configuration change)". I am not performing any action in re-creating the activity so this is of no use.
So, I guess my main question is: is there a way to preserve the View state (simply retain the Fragment) when using replace? There is FragmentTransaction.add(), but there are a few issues with this as well. One being that the exit animation is not performed, thus the animation is not correct. Another is that the new Fragment that the old fragment (the one that is being put into a non-visible state) is still clickable. For example, if I have a ListFragment, and I place a content fragment on top of that by using add, I can still click the list items in the ListFragment.
Without being able to see the code of your fragments this is a bit of a guess, but in the past I've run into this same issue and I've found that resetting the adapter in your ListFragment in onViewStateRestored seems to do the trick.
public void onViewStateRestored (Bundle savedInstanceState)
{
super.onViewStateRestored (savedInstanceState);
setListAdapter(new ArrayAdapter(Activity, R.layout.nav_item, objects));
}
Which is weird considering the documentation states that this method is called after onActivityCreated but before onStart. But it seems that it is also called at other times because when the most recent fragment transaction is popped off the back stack this method is called before the previously replaced fragment is displayed. The activity that owns the fragments has not been paused or obscured in any way, so according to the docs onViewStateRestored should not be called since just the fragments were modified. But this seems to work anyway.
It sounds like you simply need to make sure you have properly implemented onCreateView and onDestroyView. The situation you are describing seems to indicate that when the list fragment is put on the back stack (as a result of the replace transaction) Android is calling onDestroyView to free up some resources. However, it apparently has not destroyed the list fragment because when you tap back you are getting back the same instance of the fragment.
Assuming this is all true then, when the user taps back Android will call onCreateView. Any state that you have stored in the fragment's instance variables should still be there and all you need to do is repopulate the view...perhaps set the adapter on the ListView or whatever.
Also make sure your onSaveInstanceState() callback actually does save any instance state that you need to rebuild the view. That way if the fragment actually does get completely destroyed the FragmentManager can restore the state when it needs to recrete the fragment later.

Fragment backstack bug when replacing fragments in a transaction?

Here's the scenario that causes problems:
I start an Acitivity with a ViewGroup that'll hold the presented fragments.
I load Fragment A into this ViewGroup by calling .replace() in the transaction that I save onto the backstack.
I load Fragment B into the Viewgroup, replacing Fragment A. Again, I save the transaction.
I rotate the device twice.
On the second rotation, Fragment A (which is not visible at the moment) will throw a NullPointer exception.
This exception is thrown by Fragment A, because I'm saving some values of Views (EditTexts e.g.) in its onSaveInstanceState() method. It seems, that on the second rotation, the system doesn't instantiate these Views, so when I'm asking their values, I get a NullPointer exception. Can this be avoided somehow? Or is using .replace operations in a fragment transcation saved onto the backstack unadvised?
I've had this but can't quite recollect the specifics of what I did to fix but generally speaking (and apologies for the brain dump) I do the following:
Call setRetainInstance(true) in onCreate to avoid fragment recreation
Save the state of edit controls etc. in onSaveInstanceState to be used if activity is killed and you get restored with a non-null bundle (you shouldn't get a non-null bundle on an orientation change with 1.)
Maintain edit control values in member variables (as the fragment is not going to be recreated) ensuring they get updated in an onDestroyView from the edit controls and then use them to restore the edit control values in onCreateView
Have a flag which I set to true in onCreateView and false in onDestroyView and ensure I don't touch UI controls when the view is not around.
BTW Using replace while adding the transaction to the back stack is perfectly OK.
Hope there's something in there that helps. Peter.

Categories

Resources