I've read that setting .setOnRetainInstance(true) on fragments presenting UI may lead to memory leaks.
Could somebody please explain why and how this would happen? I didn't find a detailed explanation anywhere.
In a Fragment with UI you often save some Views as instance state to speed up access. For example a link to your EditText so you don't have to findViewById it all the time.
The problem is that a View keeps a reference to the Activity context. Now if you retain a View you also retain a reference to that context.
That is no problem if the context is still valid but the typical retain case is restarting the Activity. Very often for a screen rotation for example. Activity recreation will create a new context and old contexts are intended to be garbage collected. But it can't be garbage collected now since your Fragment still has a reference to the old one.
Following example shows how not to do it
public class LeakyFragment extends Fragment {
private View mLeak; // retained
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setRetainInstance(true);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
mLeak = inflater.inflate(R.layout.whatever, container, false);
return mLeak;
}
#Override
public void onDestroyView() {
super.onDestroyView();
// not cleaning up.
}
}
To get rid of that problem, you need to clear all references to your UI in onDestroyView. Once the Fragment instance is re-used you will be asked to create a new UI on onCreateView. There is also no point in keeping the UI after onDestroyView. The Ui is not going to be used.
The fix in this example is just changing onDestroyView to
#Override
public void onDestroyView() {
super.onDestroyView();
mLeak = null; // now cleaning up!
}
And besides keeping references to Views you should obviously not keep references to the Activity (e.g. from onAttach - clean on onDetach) or any Context (unless it's the Application context).
setRetainInstance(true) is used to retain instances of dynamic Fragments during an Activity recreation, such as a screen rotation or other config changes. This does not mean the Fragment will be retained forever by the system though.
When an Activity is terminated for other reasons, such as the user finishing the Activity (i.e. pressing back), the Fragment should be eligible for garbage collection.
Be careful when retaining certain objects that are coupled to the Activity.
Caution: While you can return any object, you should never pass an object that is tied to the Activity, such as a Drawable, an Adapter, a View or any other object that's associated with a Context. If you do, it will leak all the views and resources of the original activity instance. (Leaking resources means that your application maintains a hold on them and they cannot be garbage-collected, so lots of memory can be lost.)
http://developer.android.com/guide/topics/resources/runtime-changes.html#RetainingAnObject
The "setRetainInstance" is used to maintain the state of the fragment when the activity is recreated.
According to the official documentation: if we use "setRetainInstance", 2 methods of the fragment's lifecycle will not be executed (onCreate, onDestroy).
However, the views contained in the fragment will be recreated, and that is because the lifecycle will be executed from the "onCreateView".
In these cases, if we have saved some data in "onSaveInstanceState", we should ask for it in the "onActivityCreated" instead of in the "onCreate".
Oficial info: https://developer.android.com/reference/android/app/Fragment.html#setRetainInstance(boolean)
More info: https://inthecheesefactory.com/blog/fragment-state-saving-best-practices/en
you can overide onDestroy() and invoke garbage collector.
#Override
public void onDestroy() {
super.onDestroy();
System.gc();
System.gc();
}
Related
Which way is better programming for memory management when dealing with Android Views? I believe the second way is better because the TextView is only accessed when needed and then, hopefully, garbage collected. Would love to hear your views!
public class MainActivity extends Activity {
TextView tvHelp;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tvHelp = (TextView) layout.findViewById(R.id.ivHelp);
tvHelp.setText("Started");
}
#Override
public void onResume() {
super.onResume();
tvHelp.setText("Resumed");
}
}
Or this
public class MainActivity extends Activity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvHelp = (TextView) findViewById(R.id.ivHelp);
tvHelp.setText("Started");
}
#Override
public void onResume() {
super.onResume();
TextView tvHelp = (TextView) findViewById(R.id.ivHelp);
tvHelp.setText("Resumed");
}
}
It doesn't make that much difference. The first one will help you avoid multiple calls of findViewById() which is CPU consuming. The second one will help you preserve some bytes on the heap. But by doing this you will also create unreferenced objects which will stack on the heap until the GC passes (which is CPU consuming). GC will also starts when the memory is running out (when you create unreferenced objects for example). So the first solution is definitely the best one to use.
You'll avoid:
Code repetition
Multiples calls to findViewById
Unreferenced objects which will stack in the heap until the GC passes
In your case, it won't really make any difference as far as memory consumption. The Activity is going to hold a reference to that view through its hierarchy until it's destroyed whether you keep a reference to it or not. Releasing your reference to it won't make it be garbage collected.
Once the Activity goes through onDestroy(), it and its view hierarchy will be garbage collected anyway, so for this case I wouldn't worry about the difference.
One case where this can make a difference is in Fragments where the View lifecycle differs from the component lifecycle. Holding a reference to a View from onCreateView() or onViewCreated() can temporarily cause additional memory usage once they go on the backstack. You can release the references in onDestroyView() since they won't be valid anyway -- unless you are keeping the whole view hierarchy around manually.
For more info: https://stackoverflow.com/a/26370042/321697
Fragment losing state and shows an empty UI if left idle for 20 minutes. I'm using FragmentStatePagerAdapter and I have tried calling the notifyDataSetChanged in onStart() of my FragmentActivity.
Kindly help me how to save the object and state of my fragment and reuse it on reload of the app.
Android can kill your app if needed, you need to use onSaveInstanceState to keep your state in this cases. (Remember: Save important data in onPause!)
onSaveInstanceState exists in Activity and Fragments and is used in the same way like an activity
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt("integer", anInteger);
}
Now in onCreate, onCreateView or onActivityCreated you have this argument BundlesavedInstanceState which corrisponds to the bundle saved. (Check if it's null too.)
If not enought maybe Android killed your FragmentManager too, so you need to override onSaveInstanceState and onRetoreInstanceState in your Activity and restore the fragment.
Maybe this answer could help you about the last thing i said: Using onSaveInstanceState with fragments in backstack?
A Fragment's life-cycle is closely tied to the Activity's lifecycle. This means, when your Activity goes idle; it will kill off any contained Fragments. To store Fragments you could always retain them in concordance with the Fragment API. This means you will generally be using the Fragment in a background. However the best way to keep a from being destroyed or lost from an Activity's end would be to store relevant information in a custom object and then to recreate the Fragment when the Activity is resumed.
For instance; I could have a custom object that would store relevent UI values for my Fragment and when my Activity either idles or changes I would save those relevant values to my custom object that I created. Then, when either a new Activity is created; or my old Activity is resumed; I would retrieve those values and put them back into my Fragment's UI. Hoped this helped :)
In case android needs memory, it kills the running apps. So you must save the objects using
public void onSaveInstanceState(Bundle savedState) {}
Note that savedState must be serializable.
You must call notifyDataSetChanged() in onResume(), because it ensures that it is called when the activity resumes.
For a detailed answer, please post your code.
Hard to answer without your code.
However I can say that the state is usually saved by the savedInstanceState
Usually in the onActivityCreated you have something like the following. In the example I give I save a boolean of either text was entered or not.
#Override
public void onActivityCreated(Bundle savedInstanceState) {
if (savedInstanceState != null) {
mHasText = savedInstanceState.getBoolean(HAS_TEXT_TAG);
} else {
// do something
}
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putBoolean(HAS_TEXT_TAG, mHasText);
}
it's just an example as without your code it's difficult to anwer
im wondering how androids garbage collector works with data that is kept by the fragment (retain instance).
If I have a Class hirarchy like this:
class MyFragment extends Fragment {
private DataManager dataManager;
public MyFragment(){
setRetainInstance(true);
}
public void onCreate(){
if (dataManager == null)
dataManager = new DataManager();
dataManager.setView(this);
}
public void onCreateView(){
// display the data of the dataManager
}
public void onStop(){
dataManager.setView(null);
}
}
class DataManager implements DataChangedListener {
private MyFragment view;
private Data data;
public DataManager(){
data.setDataChangedListener(this);
}
public void setView(MyFragment v){
this.view = v;
}
}
class Data {
public void setDataChangedListener(DataChangedListener l){
this.listener = l;
}
}
So what i want to do is, that on orientation change the fragments view content will be recreatd. But the underlying data (DataManager and Data) must not reload.
The DataManager listen to the Data Object for changes, an will forward this changes to the UI, the Fragment. Fragment is "attached" to the DataManager when its (re)created.
So far so good. It seems to me a good Solution and well structured. Basically its some kind of Model-View-Presenter pattern.
But now im wondering, when Android will run the garbage collector to collect the DataManager and Data Objects.
Assume I have an Activity that displays MyFragment.
But what happens to the memory, when the user of the App navigates to another Activity, that displays something completely different.
Since there is a reference from DataManager to Data and vice versa, I guess that the data will be kept "forever" in memory, right?
So I guess the garbage collector will only remove this both when the device is going on low memory. But I guess, that these two objects would not be automatically the first two data objects that were garbage collected. I guess there is some kind of "memory deadlock".
What do you think? Any suggestions?
Regarding Garbage Collector:
I might be wrong on some concepts here but, as far as I know, the Garbage collector starts from a known object and "navigates" down the object tree, if any object is not on that tree, it will be collected.
So for example, it stats from the activity, and it have references to a few views, an adapter, and to the FragmentManger, the FragmentManager have reference to a 3 fragments, those fragments to a few data objects, and so on.
But if Object_A have ref to Object_B and Object_B to Object_A but no one else have reference to Object_A or Object_B, you can kiss good bye to those two.
Suggestion:
Usually to simplify the whole setRetainInstance(true); deal, I make a rule for myself (remember, that's not obligatory, but it does make life easier to understand and abstract): That if I want/need to retain data and will use setRetainInstance that one fragment does not have a view. That means, I do not override it's onCreateView and the transaction on it it's a simple add(mFrag, MyFrag.TAG);, not placing it anywhere in layouts.
That simplifies because you know that all the views are re-created when needed and you can always access your data using getFragmentManager().findFragmentByTag(MyFrag.TAG);
And back to the GC deal, with that approach you know that your data will not be GCed because it's safely kept by the FragmentManager.
sounds like a good plan? what do u think?
edit:
Further suggestion:
do not keep any reference to any View, or Activity, or Context in your DataFragment. Any long living object should not keep those references as they do hold the whole activity and that's a huge memory leak.
I have a question regarding DialogFragment. I am trying to make a dialog that keeps it's state after the device is rotated. This dialog has a bunch of references to things such as adapters and other heavier objects and I need this to be kept upon rotation, if possible without having to make every reference Parcelable or Serializable in order for me to use onSaveInstanceState to save and restore them when the original activity is re-created.
I've noticed there's a method called setRetainInstance(boolean) on the DialogFragment which allows you to keep the dialog fragment instance when the activity is re-created. However, when I rotate the device now, the dialog is not showing anymore. I know I can get it from the activity's FragmentManager, but I cannot find a way to make it visible again. Any suggestions on this?
Thanks,
Mihai
There are few things you need to do :
use instance factory method to initiate a DialogFragment instance like this :
public static MyDialogFragment newInstance(MyModel model) {
MyDialogFragment myDialogFragment = new MyDialogFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("MODEL", model);
myDialogFragment .setArguments(bundle);
return myDialogFragment;
}
by putting setRetainInstance(true) in onCreate, all of your references declared in the fragment will be kept after the original activity is re-created
#Override
public void onCreate(Bundle icicle) {
this.setCancelable(true);
setRetainInstance(true);
super.onCreate(icicle);
}
avoiding disappear on rotation by doing this
#Override
public void onDestroyView() {
if (getDialog() != null && getRetainInstance())
getDialog().setDismissMessage(null);
super.onDestroyView();
}
get your object by using
(MyModel) getArguments().getSerializable("MODEL")
The dialog fragment should be preserved automatically as long as you do the following:
If you call an Activity onSaveInstanceState(), make sure you call the super function!!!!. In my case, that was the key. Also make sure you do the same thing in the Fragment.
If you use setRetainInstance, you need to manually store off the values. Otherwise, you should be able to not worry about it, in most cases. If you're doing something a bit more complicated, you might need to setRetainInstance(true), but otherwise ignore it.
Some people have complained about a bug in the support library, where a dismiss message is sent when it shouldn't be. The latest support library seems to have fixed that, so you shouldn't need to worry about that.
Please correct me if I'm wrong on any of this. This is a kind of clarifying question since I haven't seen it explicitly written anywhere.
In Android 4, you can call setRetainInstance(true) on a Fragment so that on configuration changes (which basically means device rotation), the Fragment java object isn't destroyed and a new instance of it isn't created. That is, the instance is retained.
This is much more sane and less infuriating than in Android 1-3 since you don't have to deal with onRetainNonConfigurationStateInstance() and bundle up all your data so it can be passed to the new Fragment (or Activity) instance only to be unbundled again. It's basically what you would expect to happen, and arguably how it should have worked for Activitys from the beginning.
With setRetainInstance(true) the view is also recreated (onCreateView() is called) on rotation as you would expect. And I assume (not tested) that resource resolution (layout vs layout-land) works.
So my question is two-fold:
Why wasn't it like this with Activities from the beginning.
Why isn't this the default? Is there ever any reason why you would actually want your Fragment to be pointlessly destroyed and recreated on rotation? Because I can't think of any.
Edit
To clarify how I would do it:
class MyFragment extends Fragment
{
// All the data.
String mDataToDisplay;
// etc.
// All the views.
TextView mViewToDisplayItIn;
// etc.
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setRetainInstance(true);
mDataToDisplay = readFromSomeFileOrWhatever(); // Ignoring threading issues for now.
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
return inflater.inflate(R.layout.my_fragment, container, false);
}
#Override
public void onViewCreated(View view, Bundle savedInstanceState)
{
// At this point if mViewToDisplayItIn was not null, the old one will be GC'd.
mViewToDisplayItIn = view.findViewById(R.id.the_text_view);
mViewToDisplayItIn.setText(mDataToDisplay);
}
// Optionally:
#Override
public void onDestroyView()
{
// All the view (and activity) to be GC'd.
mViewToDisplayItIn = null;
}
}
so that on configuration changes (which basically means device rotation)
And changing locale, changing SIMs, changing default font size, plugging in or removing an external keyboard, putting the device in a dock or removing it from same, etc.
you don't have to deal with onRetainNonConfigurationState()
That's onRetainNonConfigurationInstance().
bundle up all your data so it can be passed to the new Fragment (or Activity) instance only to be unbundled again
Your data should already be "bundled" (e.g., instance of private static inner class) and therefore it would not need to be "bundled" or "unbundled". Also, it frequently should not be "all your data", unless you are a fan of memory leaks.
And I assume (not tested) that resource resolution (layout vs layout-land) works.
Correct.
Is there ever any reason why you would actually want your Fragment to be pointlessly destroyed and recreated on rotation?
Sure.
As you note, all widgets are recreated, so data members tied to widgets are not only unnecessary to retain. Unless you specifically reset those to null on a retained fragment somehow, until onCreateView() is called again, those data members would hold onto the old widgets, which would hold onto the old activity instance, which would prevent that old activity instance from being garbage collected. AFAIK, onCreateView() is not going to be called until the fragment is going to be redisplayed, which may not be for quite some time (the fragment is not used in the new orientation, or the fragment is for some page in a ViewPager that the user visited in the old orientation but does not revisit in the new orientation, etc.). That means that the retained fragment may keep the old activity object around for a substantial period of time. Depending on what else that activity may have held onto (e.g., large Bitmap objects), that could be bad.
Similarly, a fragment that itself holds onto large data, where that fragment may or may not be used after the configuration change, is one that should not be retained.
Also, there will be fragments that simply have nothing needing to be retained (e.g., all data is populated by Loaders, which are already aware of configuration changes and handle them appropriately).
And so on.
A default of fragments being not retained is the safest course of action, with respect to garbage collection issues. You can opt into having some fragments be retained, but then the onus is on you to make sure that you are not screwing yourself by doing that.
I don't know the answer to the first question. It should have been like that from the beginning. I guess someone at Google thought they're really smart coming up with this scheme.
The second question, however, is much easier. This isn't the default because this isn't what Android develops have learned to expect. Android develops know the instance dies on rotation and expect it. Changing the default would have made a lot of developers really pissed off.