Why is the reference to the Context a memory leak? - java

According to Romain Guy this kind of code is prone to memory leak due to the fact that
.... views have a reference to the entire activity and therefore to
anything your activity is holding onto; usually the entire View
hierarchy and all its resources.
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
I am not clear about this.
Assuming an application with 1 activity, this is the longest lived object and can be recreated as needed. Which means that all of its instance fields (which can and usually are Views) can be null at any time.
And any static instance field will live for the same duration as the activity itself.
So how can we get a memory leak with code like the above or the following:
private static Drawable sBackground;
#Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
if (sBackground == null) {
sBackground = getDrawable(R.drawable.large_bitmap);
}
label.setBackgroundDrawable(sBackground);
setContentView(label);
}

Assuming an application with 1 activity, this is the longest lived object
No, it is not. There are other objects in your process (e.g., Application, content providers) that will outlive an activity instance.
In particular, note that activities get destroyed and recreated by default on a configuration change (e.g., screen rotation).
And any static instance field will live for the same duration as the activity itself.
No. Static fields are around as long as the process is around. Your activity instances can be shorter-lived than that.
So how can we get a memory leak with code like the above or the following:
There is no static field in your first example.
Romain Guy explains the second scenario in the blog post that you linked to:
This code is very fast and also very wrong; it leaks the first activity created upon the first screen orientation change. When a Drawable is attached to a view, the view is set as a callback on the drawable. In the code snippet above, this means the drawable has a reference to the TextView which itself has a reference to the activity (the Context) which in turns has references to pretty much anything (depending on your code.)
And, if you added LeakCanary to your project, you would see the leak.
So, let's walk through this:
User taps on the home screen launcher icon for your app, which is tied to this activity
Your process is started
Your activity instance is created, and is then called with onCreate()
sBackground is null, and so you assign it the getDrawable() result
Your activity UI appears on the screen
The user sneezes and accidentally rotates the screen of the device as part of reacting to the sneeze
Your old activity instance is destroyed
A new activity instance is created, and is then called with onCreate()
sBackground is not null, and so you leave sBackground alone
And you have your leak. As Romain explained, sBackground has a not-very-obvious reference back to your original activity instance. So, now you have two outstanding instances of this activity: the leaked original, plus the new one created due to the configuration change.

TLDR This is no longer a memory leak, since 2010. Also, I would have said this was a bug (memory leak) in Android (since this reference was an implementation detail), not in your app. We didn't even know that setBackgroundDrawable (deprecated in favor of setBackground) calls setCallback which sets a strong reference to the view. Here I explain why Drawable has a reference to View anyway.
Romain Guy made a fix to the library in 2010 to prevent this from causing a memory leak by using a WeakReference, so this hasn't been a problem for many years:

Related

UI object created through XML are not being sweeped in Android Marshmallow

I am working on porting android marshmallow for some hardware. The problem I am facing is, if I create a UI object via xml, it is not being sweeped by GC.
For example:
I've created an app to check this.
This app is having 2 activities.
Each activity has a button.
If I press this button, it finishes current activity and starts other activity.
If I define a button in xml layout and in activity setcontentview, it'll create an object of type button. Now I am toggling these activites again and again to create multiple objects of button, since I am not using any LAUNCH_MODE(Intentionally).
Now lets say 8 objects of Button has been created. I pressed back button multiple times and came to home screen. I initiated GC from Android Studio. After that if I take Heap Dump, still I find 8 instances of Button but "ids of objects are changed". Moreover, I saw that constructor of Button class not being called after I initiated GC.
So who created these objects. And if these are same objects, why ids has been changed.
Same thing if I do by creating Button object using Button java class,
like Button b = new Button(Context c);
objects are being sweeped.
Please put some light if you understand the problem.
BR,
Rahul
You have not provided any code examples for what you are talking about. I can only guess that you are actually creating these Button objects as you mentioned,
Button b = new Button(this);
If you are referencing a button widget in XML then you should not be creating any Button objects, you should only be instantiating the object.
Button created in XML
<Button
android:id="#+id/btn_action"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
Instantiating button in code
Button btnAction = (Button) findViewById(R.id.btn_action);
This button object should not have multiple id references. When you assign an id to this widget in XML, the reference id is immediately added to your R.java file. Check your R.java file you will see something like
public static final int btn_action=0x7f0d033b;
This is a final value, it cannot be changed. So every time you reference the object, btnAction, you are referencing 0x7f0d033b. Destroying your Activity will destroy this object unless you are doing something odd like create static objects.
Also note that there is no guarantee that GC will get called immediately after an Activity is destroyed. It is mostly called when necessary. If you want to help "force" GC to be called when an Activity is destroyed then do clean up in onDestroy() of the Activity. Cheers!
#Override
protected void onDestroy() {
// invoke garbage collector
System.gc();
}
Also keep in mind that calling System.gc() does not necessarily mean Garbage Collector will be called. The definition for this is
Indicates to the VM that it would be a good time to run the garbage
collector. Note that this is a hint only. There is no guarantee that
the garbage collector will actually be run.
No need to new a Button, the button is already defined in manifest,just using findViewById to look it up.

Should I be closing my activities as I go through my app or will this cause Memory Leak? OutOfMemory Errors with drawables in layouts

I am currently developing a game with the android development enviornment. And for the past couple of months I've been dealing with a nasty OOM error. My first problem was that I was placing my drawables in the wrong folder (Drawable-xhdpi in drawable folder). But now, the OOM error eventually happens as you go through the game.
It is a rpg, basically compoed of menus in activity layouts with animations and things. and I've tried everything I could to fix it. I've tried the unbindDrawables method:
unbindDrawables(View view) {
if (view.getBackground() != null) {
view.getBackground().setCallback(null);
}
if (view instanceof ViewGroup) {
for (int i = 0; i < ((ViewGroup) view).getChildCount(); i++) {
unbindDrawables(((ViewGroup) view).getChildAt(i));
}
((ViewGroup) view).removeAllViews();
}
}
which does help a lot, but it does not fix the issue. And I cannot use any of the bitmap.factory options or anything, since I load my images through xml in my drawables folder. My images aren't that big by the way, as activities have a background of 720x1280, with some smaller images, and the most total images I'll have on screen at a time is around 8.
So this lead me to think that I may have a memory leak. I did ALOT of research, and I found out that use this(the activity context) will cause a leak, and I should use the application context. However, If I make the switch, there is almost no difference.
So I used MAT to figure out what was going on, and most of my memory is going to byte[], android.graphics.bitmap. And if I drill down to find the cause of this, it seems that java.ref.finalizer is causing all of the retained memory in the VM.
The only reason I could think this is happening, is because whenever I start a new activity I use,
Intent fight = new Intent(this, StartScreen.class);
//add this flag to remove all previous activities
fight.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(fight);
finish();
which opens a new activity, but closes the one we were just in. So, I'm guessing the bitmaps are not being recycled correctly whenever I finish an activity? or is closing and starting activities like this bad for memory?
I've been on Google all day trying to find the solution to this problem and I can't find it. Any soulutions are appreciated, thank you for reading this!
P.S if you would like to see any snippets of my logcat or code or anything, I am more than happy to post it.
P.S P.S My game has about 10-12 different activities I switch between. For example If I have activities A,B,C I open A, Open B close A, Open C close B, open B close C, open A close B.
EDIT: As request about my activities. Usually it is a menu, and when you press a button, that activity finishes, and then moves into another activity. Or buttons will do some math for things like selling, or doing damage to an enemy. One thing about my activity architecture, is that since I am closing every activity as I go to a new one, when I go back to the ones I closed, I am re-creating them. So I don't know if the old activities I finished still have memory in the VM that over time causes the Out Of Memory error, since it all builds up and keeps expanding. I explained My call for a new activity above. And the intent flag closes all past activities (if there are any) in the stack.
EDIT EDIT: As per request my oncreate and onDestroy:
OnCreate:
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_start_screen);
//setting fonts
//a function that binds views by findview by Id and then sets their typeface
setFont();
//set up the music service
//connects the app to the background music service
playMusic();
//aquire wakelock
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
OnDestroy:
#Override
public void onDestroy()
{
super.onDestroy();
//unbinds the service
unbindService(musicConnection);
//unbind drawables (function above)
unbindDrawables((LinearLayout) findViewById(R.id.container));
}
you should destroy the activities that you don't need anymore it takes place in the memory. or if you dont want your user/player to go back to the recent activity you should finish the activity. and if you will notice. if you dont finish the activity and press the back button several times it is layered.
second you mentioned that it is a game. in android programming you need to consider your bitmap resources so to avoid getting an OOM error in your game make use of sprite sheet it will save a lot of memory and usage of bitmaps in your application. i encountered those kind of situation and bitmaps mainly causes the OOM error.
I will say it depends. but with your problem your answer is right here
Quick quote from the site
Note: In most cases, you should not explicitly finish an activity using these methods.
As discussed in the following section about the activity lifecycle,the Android
system manages the life of an activity for you, so you do not need to finish your own
activities. Calling these methods could adversely affect the expected user experience
and should only be used when you absolutely do not want the user to return to this
instance of the activity.
Read for more info
well i get you lucidly now, but im thinking of what you are tryna do here, so y dont you use FragmentActivity for B and C.. So, Activity A opens B..B is opened as fragment but works like activity-(thats fragmentActivity), and opens C which is most likely a Fragment..which i think would be perfect for your situation..
for more info about FragmentActivity click Here
no more activities back and forth.. and its gonna work like an activity..

v4.app.Fragment when is it created and destroyed?

How exactly v4.app.Fragment works? I have viewPager with 7 Fragments. I was sure that when moving between fragments, depending on the situation each fragment can be automatically destroyed and created. However it seems not to be true.
I decided to play a bit with debugging and set some flag for my Fragment class object, something like
class MyClass extends Fragment {
public boolean myFlag=false;
When I set ex. true somewhere in the code which is run only once (ex. true is set after button click), it seems that this value is true until the app ends. Thus it suggest the object is kept all the time in memory.
However when user moves between Fragments onCreateView and onViewStateRestored methods seems to be called.
And now I feel quite confused. If Fragments are not destroyed, why those methods are executed by Android?
Do I have the guarantee that my Fragment object will be kept always with all fields (when user only moves beteween Fragments and doesn't leave the app)?
If not how should I save and restore its state? public void onSaveInstanceState(Bundle savedInstanceState) method seems to be only run when user left the app, not when user moves between Fragments.
Have you seen any good tutorial concerning ViewPager and Fragments?
And now I feel quite confused. If Fragments are not destroyed, why
those methods are executed by Android?
If you look at this diagram, Fragments do not need to be destroyed to call onCreateView().
Do I have the guarantee that my Fragment object will be kept always
with all fields (when user only moves beteween Fragments and doesn't
leave the app)?
This depends on what you are doing. If your case is dealing with ViewPager it really boils down to what PagerAdapter you use and your ViewPager configuration.
If not how should I save and restore its state? public void
onSaveInstanceState(Bundle savedInstanceState) method seems to be only
run when user left the app, not when user moves between Fragments.
It depends under what conditions you are wanting to restore state. For example, for orientation changes you can call setRetainInstance(true) on the Fragment and the Fragment's state will be preserved when there is a configuration change given your Fragment is not on the backstack.
Have you seen any good tutorial concerning ViewPager and Fragments?
The Android Guide has a good tutorial.
You can re-initialize all the values of a specific fragment in it's onCreate(...) method. So that when ever it is navigated to, it will have the default values that you setup in your onCreate(...) method.
Here are some useful links related to the Implementation of Fragments in a ViewPager:
1. http://thepseudocoder.wordpress.com/2011/10/13/android-tabs-viewpager-swipe-able-tabs-ftw/
2. http://androidtrainningcenter.blogspot.co.il/2012/10/viewpager-example-in-android.html
3. http://manishkpr.webheavens.com/android-viewpager-example/
4. http://thepseudocoder.wordpress.com/2011/10/05/android-page-swiping-using-viewpager/
I hope this helps.

Android activity state, unable to get information saved

There are a bunch of other questions about this topic, but I have not been able to figure this issue out.
In the Android documentation (http://developer.android.com/training/basics/activity-lifecycle/recreating.html) it says:
By default, the system uses the Bundle instance state to save information about each View object in your activity layout (such as the text value entered into an EditText object). So, if your activity instance is destroyed and recreated, the state of the layout is restored to its previous state with no code required by you.
So I tested this in the emulator by simply creating a view that contains a EditText-view. I then enter information into it and press the home button. When I reopen the app, the information is gone. Shouldnt this be persisted automatically or am I missing something?
Well you are partially wrong and partially right. You are wrong, because the quotation in grey is taken out of the context. I'll explain briefly, by making the correct quotations from the link you provided:
When your activity is destroyed because the user presses Back or the
activity finishes itself, the system's concept of that Activity
instance is gone forever because the behavior indicates the activity
is no longer needed. However, if the system destroys the activity due
to system constraints (rather than normal app behavior), then although
the actual Activity instance is gone, the system remembers that it
existed such that if the user navigates back to it, the system creates
a new instance of the activity using a set of saved data that
describes the state of the activity when it was destroyed.
Now, after that paragraph we have a clarification:
Caution: Your activity will be destroyed and recreated each time the
user rotates the screen. When the screen changes orientation, the
system destroys and recreates the foreground activity because the
screen configuration has changed and your activity might need to load
alternative resources (such as the layout).
Another one, several linew below is:
To save additional data about the activity state, you must override
the onSaveInstanceState() callback method. The system calls this
method when the user is leaving your activity and passes it the Bundle
object that will be saved in the event that your activity is destroyed
unexpectedly. If the system must recreate the activity instance later,
it passes the same Bundle object to both the onRestoreInstanceState()
and onCreate() methods.
This Bundle (Bundle savedInstanceState) is used, when the application accidentally crashes OR if the rotation of the screen is enabled (to name few), which is also destoying (then recreating) your foreground.
You can also take a look at the following section "Save Your Activity State", but I would recommend you this link here.

May I store View objects in instance fields?

I've got an Activity class. It should be really nice to find all the views I need in onCreate, and then just reference these fields, without calling findViewById. But is it OK to do so?
Can't views be assigned different objects at runtime? E.g., is it always true that findViewById(res1) == findViewById(res1) at any time?
You can create instance variables for your views in an Activity. And
findViewById(res1) == findViewById(res1)
is true as long as the layout is not inflated again or other changes (replacing views) are made to the content view.
But do not keep references to views in objects that will live longer than the activity holding the views. Like in an Singleton! (see see Avoiding Memory Leaks)
Yes you can do this. This is how I've seen it done 90% of the time at my work. Ex.
private ImageView mSomeImage;
public class activ extends Activity{
public void onCreate(Bundle saveinstancestate){
Inialize your views here.
}
}
Only do this if you need to, and prefix member variables with m.
Does this help?

Categories

Resources