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.
Related
I'm building my first Android app in Android Studio (with Java) and I need some help understanding how to pass data through multiple activities.
The setup of affected classes/activities is this:
MainActivity: Opens a dialog called AddDialog (by creating a new Instance of it).
AddDialog (extends AppCompatDialogFragment): A dialog that has some buttons. One of them launches a class called BarcodeScanActivity (using intent).
BarcodeScanActivity: Simple activity that scan QR codes.
I want to pass the list of QR codes scanned by BarcodeScanActivity (I store them in a String Array) to MainActivity in order to use them, although BarcodeScanActivity is launched from the dialog that gets destroyed once buttons are clicked. Because of that I'm unable to set some startActivityForResult on the dialog and chain the result (onActivityResult) back to the MainActivity.
Also since MainActivity launches the AddDialog by creating an instance, I can't set a startActivityForResult there too.
I have tried adding Intent.FLAG_ACTIVITY_FORWARD_RESULT while launching BarcodeScanActivity from AddDialog, hoping that the result from BarcodeScanActivity will be forwarded back to MainActivity where I have created an onActivityResult method since AddDialog gets destroyed, but I don't know if that is even supposed to work as AddDialog is "launched" by a new instance of it, and not by using an intent.
I have thought about using broadcasts as a last resort, although I read that they are insecure, unreliable and not supposed to be used for passing that kind of data.
Any help is highly appreciated! Thanks
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:
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..
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.
I understand the concept of the back-stack, so I'm pretty sure this isn't possible but thought I'd ask anyway.
If it isn't, then what is the approach to simulate this behavior? For instance, I have an Activity "A1" that starts another Activity "A2". "A2" alters the content that "A1" shows. When the back button is pressed, the old "A1" is displayed with the old content. Whenever "A1" is called again then the new "A1" will show the new content.
How do developers get around this issue?
When a user press the back-button the A1 activity comes back to the foreground. This will not trigger the onCreate() so you can't use that but if you look at the Activity Lifecycle the onResume() method will be called.
So if you move the displaying of the content in activity A1 from the onCreate() to the onResume() method it should work fine in both situations, when the activity is started and when you return to the activity using the back-button.
Check the Activity Lifecycle. When an activity is made visible it will go through onStart and then onResume when it gains focus. You can load new content at one of these points instead of in onCreate if you would like to update whenever the user navigates there.