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?
Related
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:
Instead of moving views in a current layout, I was wondering if i could instead load a different layout whilst the program is running.
For example in the on create i would use:
setContentView(R.layout.layout1);
and then in an on click listener i would use:
setContentView(R.layout.layout2);
I say this since I am using a custom dialogue which prevents me from producing a dialog to overwrite it. I have attempted it but so far have only received errors. I would really like to know if this is possible.
This is possible, but risky and not recommended for a final product. The problem is, you cannot access already defined views once you have switched views. You need to assign them all again for the new layout. So once you switch the content view, re-initialize all of your views and anything that references the old layout. Calling a view from the old layout will cause a crash or error message.
Like CodeMagic said, it is best to use fragments and the FragmentManager for this. And really, this is not a stable way to produce good code. I recommend using separate fragments and using backstacks and such so that not only will your game work, but you can easily navigate back to the original fragment, rather than use makeshift code that may barely work.
After setContentView(R.id.yourLayout) is called, you need to re-instantiate all of your other views. So like say you used an ImageView view to show the color changes, well you need to instantiate that ImageView after you setContentView(R.id.yourLayout) so that it pulls from the new layout, and does not reference to the original layout.
Example:
ImageView imageView;
public void onCreate(Bundle b){
super.savedInstanceState(b);
setContentView(originalLayout);
//Instatiate all of your original Views.
imageView = (ImageView) R.id.yourImageView;
}
public void onButtonClick(View){
setContentView(newLayout);
imageView = (ImageView) R.id.yourNewImageView;
//All other views
}
If you need an example of the fragment manager solution go here: https://developer.android.com/training/basics/fragments/creating.html
and look through some of their examples on how to properly do what you are trying to do.
Let's say I have a Spinner view with 10 Countries (String).
I want to use this spinner in multiple activities, fragments, alertdialogs etc.
How can I do it efficiently and save code lines? Which is the best way?
What I first thought is to create a class that creates the spinner and extends the spinner widget class. Then create objects etc. However, as a begginer, it looks a bit complicated to me, is there an easier way?
EDIT:
The spinner is created dynamically ONLY. It has 10 items by default but is gradually populated according to database entries.
Create a public static method in any class (but probably one that is intuitive to find*) that populates the spinner. It can take context, database, etc. as inputs, as well as the Spinner itself. Then you can call this same method from any fragment or activity and always get the same thing. Just create your layout (such as with setContentView), get a reference to the spinner from the layout, and pass it to your populater method.
Example:
//in Activity
public void onCreate(Bundle bundle){
super.onCreate(bundle);
DatabaseHelper myDataBaseHelper = ...;//
setContentView(R.layout.my_layout);
Spinner spinner = (Spinner)findViewById(R.id.my_spinner);
Util.populateStandardSpinner(myDataBaseHelper, spinner,
getApplicationContext());
//...
}
//Another class
public class Util{
public static void populateStandardSpinner(DatabaseHelper dbHelper,
Spinner spinner, Context context) {
//Get cursor from dbHelper
//Create adapter for cursor data and apply it to spinner
}
I suppose you could also extend the Spinner class, but my preference is to avoid coding the data directly into the widget. That would break the model-view-controller design pattern.
*I sometimes just create a class called Util where I put convenient static methods like this. Or if you have a database helper class, that might be an intuitive place to put it.
You can create a separate layout file for this and only have spinner in this.
Wherever you require this you can include in your layouts like
<include layout="#layout/spinnerLayout" />
Extend Spinner class itself.
You can initialize adapter inside constructor, dynamically load database entries using AsyncTask, and manage resource usage with onAttachedToWindow and onDetachedFromWindow.
I usually do this when face similar problems, it is convenient, easy to develop and use.
I'm converting all my Activities to Fragments so that I can use them in a ViewPager.
I've searched for this but I couldn't find a satisfying answer, so that's why I'm asking it here.
In my Activities, I've written some code in the onCreate() method. I for example call some findViewById()s in order to link some xml-buttons to my Activity. I also make some views invisible in the onCreate(), set an OnClickListener(), fill a TextView with text and remove a Notification, all in the onCreate() method.
My question is: Where should I put this code in the fragment? In the onCreate()? onCreateView()? onActivityCreated()? and why?
Many thanks in advance!
Although Pragnani's answer is close, there's little educational value in it. Besides, there's a more appropriate option to his 2nd statement.
Where should I put this code in the fragment? In the onCreate()?
onCreateView()? onActivityCreated()? and why?
The short answer is: either onCreateView() or onActivityCreated() will do. The view hierarchy won't be created until onCreateView(), so that's the earliest point in the fragment's life cycle that you could inflate the views and attach click listeners etc. Since onActivityCreated() will always be run after onCreateView(), that's a suitable location too. onCreate() may be skipped in favour of the system temporarily detaching the fragment and reattaching it, e.g. when retaining fragments.
Pragnani is correct by pointing out that inflating the views of a fragment is slightly different from inflating views in an activity. More specifically: a fragment does not define a findViewById() method, so you'll need to call it on some other object.
Rather than using getActivity().findViewById(), you'll want getView().findViewById(). The reason for this is that if you use the activity for the view lookups, then you'll get into trouble when multiple fragments with the same view IDs are attached to it. This will be the case if you reuse view ids in the layouts of your various fragments, or if you show two identical fragments that display different data. In both cases, only the first match would ever be returned, whereas you really want to the view to be looked up in the conext of the fragment. That's exactly what getView() returns, the fragment's root view (that you returned in onCreateView()), and thus limits the scope of the lookup appropriately.
1.Left the onCreate empty and just call super.onCreate()
2.Instead of findViewById() use getActivity().findViewById()
always use getActivity() where you need context of the view.
Do all other operations in onCreateview()
I have two spinners, spinner A and spinner B. When the user changes A, B gets updated with a full new set of data. I also implemented a callback for B to use setOnItemSelectedListener so that I can modify some objects in another class whenever B is changed by the user.
B.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
mComponentColor.setSelection(position);
mCompColorAsBuilt[mComponent.getComponentSelection()] = position;
setColor();
}
});
The problem I've ran into is that I really don't want these objects to change unless the user was the one who changed the spinner. Because I automatically populate B based on A's selection, B's callback is invoked when the users changes A.
Any thoughts how I could deal with this situation?
I basically ended up solving this using a similar method to the flags method mentioned above, but did it inside the object that is adjusted when the onClickListener methods are invoked.
Basically by remembering what the last state of the spinner is, I do a check before I adjust any objects within it. If the state is the same, nothing gets changed. Fortunately all the object adjustments were done by another class, which made it relatively easy to handle. I'm not overly happy with it, but I think it was ultimately better than using flags within the activity class.
Thanks to those that contributed.