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
Related
Initial code:
public class SummaryActivity extends AppCompatActivity
{
private RecyclerView recyclerView;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary);
recyclerView = findViewById(R.id.summary_recycler);
...
Android Studio (v3.3) suggested to make recyclerView local to onCreate() method.
Modified code:
public class SummaryActivity extends AppCompatActivity
{
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_summary);
RecyclerView recyclerView = findViewById(R.id.summary_recycler);
...
Ran both versions multiple times and could not notice any difference. Is one better than the other? If yes, could you please explain why? I have an inkling that declaring recyclerView as a class field/member is safer, however lack the understanding to be sure enough.
I wonder what happens to recyclerView in the second version after the onCreate() method has finished executing. Would garbage collection later on suddenly destroy the object?
The closest thing I found on this topic so far was:
Member object instantiation in onCreate vs. during declaration, but it sadly doesn't touch the specific issue that I am facing.
I am sure that this matter doesn't apply to just RecyclerView objects alone and that the understanding will help me code more appropriately in the future, also in other contexts.
Would truly appreciate any clarification offered. Thanks in advance!
I wonder what happens to recyclerView in the second version after the onCreate() method has finished executing. Would garbage collection later on suddenly destroy the object?
Since the object we are talking about here is a View which is part of the Activity's "content View", it will only be destroyed if the Activity as a whole will be destroyed.
There is a difference to "normal" fields: the RecyclerView will be instantiated under-the-hood because you call setContentView(R.layout.activity_summary); and so the runtime will inflate the layout file.
In your code, you don't instantiate it but you assign it to a variable by "finding" it:
RecyclerView recyclerView = findViewById(R.id.summary_recycler);
If you keep the variable local, it may improve the readability of your code. But you should only do so if you don't need to access the RecyclerView in more than one method, because each time that findViewById() is called, the whole View tree will be searched until a View with a matching id is found.
I set listener for my button:
View onCreateView(...) {
...
btn.setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
//some work
}
});
...
}
If I use ButterKnife, it is recommended to call ButterKnife.unbind(this) in onDestroyView(){..}. Do I need to remove the listeners for avoid memory leak?
void onDestroyView() {
...
btn.setOnClickListener(null)
}
It depends...
Do you have different layouts for portrait and landscape, and you have it configured so that when you rotate only the view is destroyed?
If so, YES to prevent references of an unused view on a used Activity/Fragment (that could prevent it from being GC)
If when you rotate the view and Fragment/Activity is destroyed, then NO you don't need to do it, the GC will take care of it.
This difference exists due to the fact that Android uses a Mark-Sweep algorithm on it's GC which will prevent the cases where 2 unused objects with references to each other (circular references) from being collected... but not the cases where a used object has a referenced to an unused one.
Some useful Q&A about this:
Does setting Java objects to null do anything anymore?
Android: Is there any advantage of settings references to null when an Activity is being destroyed?
Technical details of Android Garbage Collector
How does garbage collection work in Android 4.2 Jelly Bean Dalvik?
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'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();
}
Will the following code cause a memory leak? Essentially I switch between various layouts in my application using setContentView(), and I have member variables of my activity that maintain references to various views (buttons/textviews...) on the layouts.
Am I correct in thinking that if the activity class has a reference to a button and then changes layouts the layout wont be garbage collected because it will still hold a button reference? If this is the case, can I just null the button variable before changing layouts?
Thanks.
public class MyApp extends Activity {
private Button startBtn;
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// Set main layout
setContentView(R.layout.main);
startBtn = (Button) findViewById(R.id.startBtn);
startBtn.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
doStart();
}
});
}
private void doStart()
{
// Change to starting screen layout
setContentView(R.layout.begin);
/// .. Work with more views here and change layouts in a bit .. //
}
}
You will want to set the button to null before changing any layouts.
I don't believe that should cause a memory leak. Changing the layout doesn't destroy the activity, so the activity still has control over the bound references. Once the activity is destroyed, all the memory should be cleared up. Also, you might want to think about using separate activities if you're switching layouts that much.
Views of R.layout.main (that you initialy assign in OnCreate) will not become garbage as long as you hold startBtn reference OR as long as your activity's instance is alive. Either way it doesn't look like a potential memory leak. Just make sure to release references to views when setting new layout. Yet another thing to consider is to use WeakReference to wrap references to views of your layout (that's for complex designs). This way, as soon as your layout is no longer attached to activity (no strong references to views), all views can become a garbege even though you are referencing them via WeakReference.