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.
Related
In some activity, I have to save MVC model as a parcelable. The parcelable is built according to the doc, which I've read more than enough (but who knows, I obviously could have missed something). There is a leak in this activity, but I'm struggling to understand its cause. The SO question Communication objects between multiple fragments in ViewPager was interesting but my code was already following guidelines from the answer.
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
Whenever I pause my activity, onSaveInstanceState is triggered once, and immediately after multiple triggers of my parcelable's writeToParcel method. The number of triggers depends on the number of Fragments ever loaded in the viewpager + 1. So at activity startup, if I turn the emulator off and back on, writeToParcel is called 3 times (only 1st and 2nd fragment are loaded), if I swipe once right and do it again, it is called 4 times (the 2nd is showing and the 3rd is loaded), if I setExtPosition() on the adapter and go to 10th fragment, writeToParcel is called 7 times (9th, 10th and 11h are loaded).
Of course if my user swipe every fragment, it will eventually get an ugly TransactionTooLargeException, which brings me here.
Here is some code. There may be a ton of code/concept improvements here, and any tips is very welcome, but my main problem is this dirty little leak I've found.
In my activity:
#Override
public void onSaveInstanceState (Bundle outState) {
outState.putParcelable("model", myParcelable);
super.onSaveInstanceState(outState);
}
In my fragment:
public static MyFragment newInstance(Model model) {
MyFragment fragment = new MyFragment();
Bundle args = new Bundle();
args.putParcelable(KEY_MODEL, model);
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(#Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Bundle args = getArguments();
mModel = args.getParcelable(KEY_MODEL);
}
In my parcelable model:
#Override
public void writeToParcel(Parcel dest, int flags) {
int startSize = dest.dataSize();
dest.writeString(foo); //one of these string is supposed to be null
dest.writeString(bar);
dest.writeString(foobar);
dest.writeByte((byte) (isMyObjectTrue ? 1 : 0));
dest.writeList(someStringList);
dest.writeList(someIntegerList);
dest.writeBundle(someBundle); //I use a Bundle to save a Map<String,String>
int endSize = dest.dataSize();
}
I ran the debugger inside the writeToParcel() method, and I was surprised to see that startSize is never 0. Is it normal ?
I searched throughout my code, and putParcelable() or any writing method with parcelable in its name is only called in this activity and in the fragment newInstance().
How can I find the cause of this weird exponential behaviour ?
PS: of course feel free to ask for more code.
EDIT
I've implemented the solution advised by #Ben P., and the problem have improved a lot, but is not totally solved. My activity implements an interface which now has a getModel() method called in onAttach(), and a setUserInput(char userInput) I use to update the model from the fragment. The fragment's newInstance() method don't save the model anymore.
MyFragment
#Override
public void onAttach(Context context) {
super.onAttach(context);
try {
callBack = (MyInterface) context; //callBack is a class field
mModel = callBack.getModel(); //mModel too
} catch (ClassCastException e) {
throw new ClassCastException(context.toString() + " must implement MyInterface");
}
}
This turned the exponential problem into a linear problem which is obviously better but still a problem.
Now, writeToParcel() is only called once, but the total parcel size is still growing with the number of item loaded. My model takes around 3kb inside the parcel (+/-10% depending on the number of inputs), as measured by endSize-startSize.
How can I know where the growth comes from ?
Before I get into your problem specifically, I want to point out that the Bundle passed to setArguments() is part of the fragment's instance state. Every time a fragment is destroyed and recreated, these arguments need to be persisted. So anything you put into that Bundle has the potential to be parceled and unparceled during configuration changes.
The activity own a viewpager with around 60 fragments inside (but up to 350). The model is passed from the activity to all fragments, and user actions in the fragments are saved into the model.
This sounds like you have one single Model object that all fragments share. If this is the case, I recommend not passing the model object to each fragment as part of its arguments Bundle. Doing this will cause tremendous duplication when instance state is saved and restored. Instead, I'd expose a method in your Activity (something like getModel()) and then call that from your fragments to retrieve the model instance.
On the other hand, it also sounds like maybe you're only starting with the same Model object, and that each fragment can mutate it in some way. This would mean that you do have to save something to instance state for each fragment... but it's possible you can optimize here. Rather than saving and restoring the entire Model object, perhaps you could just store the diffs. That is, if fragment #1 changes the model's name, and fragment #2 changes the model's value, then you could have fragment #1 only save the new name and have fragment #2 only save the new value. Doing this instead of saving two extra copies of the model object could potentially amount to huge savings.
Can using context in helper class cause memory leak in android
I have a helper class with the following method
public class HelperClass {
private Context context;
public HelperClass(Context context) {
this.context = context;
}
public void Addfiles(Context context, String Filename) {
try {
Intent mediaScanIntent = new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE);
File f = new File(Filename);
Uri contentUri = Uri.fromFile(f);
mediaScanIntent.setData(contentUri);
context.sendBroadcast(mediaScanIntent);
} catch (Exception e) {
e.printStackTrace();
}
}
}
In my MainActivity I want to call it like this
public class MyActivity extends Activity {
public void onCreate(Bundle savedInstanceState) {
HelperClass h = new HelperClass(this);
h.Addfiles(this,Filename);
}
}
I want to know can using context like this cause memory leaks , and if so , how to deal with it.
TL;DR - strictly in the case above, no, since MyActivity won't be destroyed while in "onCreate"
Long answer:
Typically memory leaks happen when an object cannot be garbage collected because something still references it, when the garbage collector runs.
In your case, the activity is just being created, and your primary concern is whether leaks can occur, since HelperClass holds a reference to it.
First of all, in normal conditions (excepting when AOS might kill your app) MainActivity is guaranteed to not get garbage collected before its onDestroy method will be called. This is because until then (and possibly for some time after that - not really relevant) it will be referenced by the Android Framework itself.
HelperClass on the other hand, is a local variable inside onCreate. After onCreate will finish, HelperClass might be garbage collected at any point, since it won't have any object referencing it (see here). So the chances of HelperClass being GC'd before onDestroy is called are quite high. If this happens then there will be no reference to MainActivity --> no leaks.
Event if HelperClass won't be garbage collected until onDestroy is called on MainActivity, you still won't have leaks since the garbage collector is smart enough to be able to clean up cyclic references.
What needs to happen for leaks to occur is that an object which outlives your Activity will hold a reference to it, or its context.
Assume your code would be like this:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final HelperClass h = new HelperClass(this);
//does some work on a background thread, gets notified at some
//point in the future, through the callback, when the work
//has completed
longLivedComponent.doSomeBackgroundWork(new OnWorkFinishedListener(){
#Override
public void onWorkFinished(){
h.Addfiles(this, filename);
}
});
}
Now, let's also assume the following sequence of events:
User opens app, MainActivity#1 is created, its onCreate is called
doSomeBackgroundWork is called, background work is started. When the background work finishes, your helper (which has a reference to MainActivity#1) will have to do some work.
User rotates device, an orientation change occurs.
MainActivity is recreated by Android. MainActivity#1 is destroyed (i.e. onDestroy will be called), and a new MainActivity#2 will be created
Steps 1,2 will happen for MainActivity#2 i.e. doSomeBackgroundWork will be called again, from MainActivity#2. (strictly speaking, this step isn't really relevant, but nevertheless)
The Garbage Collector runs.
The background work started by MainActivity#1 at step #2 finishes.
Normally, at step #6 the memory occupied by MainActivity#1 would be freed, as MainActivity#1 was destroyed.
However, in situation described here, the OnWorkFinishedListener needs to be persisted by longLivedComponent until the background work finishes. The listener has a reference to both your MainActivity#1 (implicit anonymous inner class reference to its parent class), and to your HelperClass instance, which also contains an instance to MainActivity#1.
When the garbage collector sees this at step #6, it assumes that MainActivity#1 is still alive & used since there stil are objects pointing to it. Because of this, it doesn't reclaim its memory, you'll end up with two instances of MainActivity in memory and a leak occurs.
To prevent this, you could:
Use the application context instead of the activity context, in classes that will be used by long-lived objects. Also, in your case you don't need anything specific to the activity itself
Make sure that any long-lived objects (like longLivedComponent in our case), or objects shared between activities, won't store references to an Activity or its Context, after onDestroy() has been called on said Activity
Hope this helps
In my Android app I have set a custom Application-derived class. In it I have a member field to store some arbitrary object in.
So I have:
public class MyApp extends Application {
public static MyApp mInstance;
public Object mData;
#Override
public void onCreate() {
mInstance = this;
}
public void setData(Object data) {
mData = data;
}
public Object getData() { return mData; }
}
Now in one Activity I'm doing
public doSetData() {
someMyData = ....
MyApp.mInstance.setData(someMyData);
}
In another Activity I'm doing
#Override
public void onCreate(Bundle) {
Object myDataRetrieved = MyApp.mInstance.getData();
}
I can see that sometimes myDataRetrieved is null. However, I believe I have never passed null in MyApp.setData(). Of course, I can be wrong.
Yet, can there be such circumstances under which MyApp.mData becomes null by itself?
Yet, can there be such circumstances under which MyApp.mData becomes null by itself?
Sure. It will happen every time Android terminates the process, which will happen when your app is not in the foreground, Android needs RAM, and your app is next in line to be terminated.
just save your data and load it up on application object creation if the instance was destroyed, which will always be created before the activity.
if you minimize the memory use then it will survive longer in the android zygote before beig purged. saveinstancestate etc are good only for small data blocks. sharing data through app instance is ok and sometimes recommended, however it is not different from using static fields. same constraints apply.
if you want minimal memory use then using the parcels etc to save your data is not recommended, not even by google.
You can never guarantee that your data will be retained in memory, since the system can always reap the process to reclaim resources.
Thus, your application must always be prepared to save any persistent data either in onSaveInstanceState() (short term) or in onPause() (long term), and then retrieve it in onCreate().
That said, there's also the "singleton pattern" which I'm quite fond of. This is a software design pattern in which you create a special class just to hold your persistent data. There's only ever once instance of the class (thus the name "singleton") which is created on demand the first time your data is needed. The singleton is retained so that any subsequent needs for the persistent data just use the same object without the need to re-load the data from long-term storage. As long as the system doesn't reap your process, the data is always there with nearly zero cost to access. If the system does reap your process, the data is transparently re-loaded, and your application never notices the difference.
See https://stackoverflow.com/a/14779357/338479 for my implementation of a singleton.
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();
}
I'm confused on this. Just started android and have a long form that needs multiple activities to bring together an object. I would like to pass the object from activity to activity to build it. After reading the many posts and blogs and the Android Dev pages, it seems for non-persistent data, the best bet is to subclass application or create a singleton. I reviewed this post openFileOutput not working properly inside a singleton class - ideas/workarounds? and now my question is this, Why doesn't a singleton ever get recycled? If we createSingleton() in Activity A, then move to Activity B and we are never passing a reference to the singleton, how does the garbage recycler know that we are going to come back to it again? it seems to me that when Activity A is recycled and we have moved to Activity B that the singleton would die..
If we look at the following singleton..
public final class SomeSingleton implements Cloneable {
private static final String TAG = "SomeSingleton";
private static SomeSingleton someSingleton ;
private static Context mContext;
/**
* I'm private because I'm a singleton, call getInstance()
* #param context
*/
private SomeSingleton(){
// Empty
}
public static synchronized SomeSingleton getInstance(Context context){
if(someSingleton == null){
someSingleton = new SomeSingleton();
}
mContext = context.getApplicationContext();
return someSingleton;
}
public void playSomething(){
// Do whatever
mContext.openFileOutput("somefile", MODE_PRIVATE); // etc...
}
public Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException("I'm a singleton!");
}
}
And we create an instance of it through getInstance(), the class places a single instance of the class into static field, someSingleton. Why is this instance never recycled? If the answer is, "Static fields are never recycled?" What keeps us from using up all of our memory if we have many of them? Simple design considerations? This seems risky if we are using lots of contributed libraries that we have no idea how many static fields are out there. I just have this feeling that there is some fundamental rule that I am missing in OOP as a newb.
The general pattern is to put a reference to your singleton class in a static field. Static fields are not tied to a particular instance so they stick around until the JVM process is alive. It doesn't really matter how many activities access it. If you need to 'recycle' the singleton, maye you don't really need to use a singleton? Or provide an explicit close()/open() etc. methods.
I think the reason that your Singleton's are not getting recycled is because activities in Android aren't destroyed when you think they are.
You pose a question like 'what happens when we move from activity A to B'. But when you do that in Android, Activity A is very rarely destroyed. It usually just goes into the onPause() state. Thus, your Activity A will still be (mostly) intact if and when a user decided to hit the back button enough times to get back to activity A.