View + Tag = memory leak? - android

The basis:
Activity - recreates(onCreate-onDestroy) on each orientatin change
View consists of ViewFlipper with two childs: simple RelativeLayout and ListView
ListView rows have complex layout and associated tags
The problem is that i have memory leak on each orientation change - activity stays in memory with whole view layout. Activity itself is a context so it'll stay in memory as long as associated objects will. So now i'm trying to find why leaks are happen.
View has setTag() method. I'm using it to store some information about rows(so every row(View) in ListView has associated tags).
But how does views and GC acts with tags ? My tag objects(holders) contains references to views but if view removes reference to it's tag this references(with tag itself) will be easily collected.
Anybody have faced similar problems with ListViews ?
P.S. i'm wondering how GC cleans layouts - tonns of cyclic references, contexts, holders, etc...

Firstly you can leak objects if you use View.setTag(int, Object) method. Tags set using this method are stored in a static WeakHashMap with a View as a key. So if you store references to child view in the parent view's tags then all these views and a context they were created with (parent activity) will be leaked. It happens because every child view holds a reference to its parent, so the parent view will never be collected by the GC.
There's a simple way to simulate this behavior:
public static class MainActivity extends ListActivity {
private final WeakHashMap<Parent, Parent.Child> mMap =
new WeakHashMap<Parent, Parent.Child>();
#Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// If parents were collected OOM error wouldn't be thrown.
// But they aren't collected so we get OOM here.
for (int i = 0; i < 10; ++i) {
Parent parent = new Parent();
mMap.put( parent, parent.mChild );
}
}
}
public static class Parent {
public final Child mChild = new Child();
public class Child {
private final byte[] mJunk = new byte[10*1024*1024];
}
}
Secondly it seems that the ListView class causes a memory leak. It means that the list view, all its recycled children and its parent activity are leaked. Here's some information about this bug:
http://code.google.com/p/android/issues/detail?id=12334
Android: AlertDialog causes a memory leak

I think you might have some non-static inner classes somewhere, which always save a pointer to their surrounding object instance. For example:
public class A {
private class B {
// ...
}
// b stores a reference to the instance of A
private B b = new B();
}
If you use the setTag() method (e.g. for a ViewHolder class), never store any references to parent objects there. In fact, you should declare this class static.
Plus, to avoid memory leaks, if possible you should always pass the result of getApplicationContext() to methods that require a Context - and no reference to the Activity itself.

Its easy to leak references to the Activity on orientation change. There are a handful of blog posts about this - which I feel are required reading:
http://ttlnews.blogspot.com/2010/01/attacking-memory-problems-on-android.html
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
http://code.google.com/p/android/issues/detail?id=2391
In a super nutshell in your onRetainNonConfigurationInstance method you just want to be careful that you null out any references to View objects and in turn Activity references, Progress bars, etc.
A good pattern I use is having a "StateHolder" inner class which does contain an Activity reference, but I implement a setActivityForTasks method, which I just pass NULL to, it in turn sets all Activity references to NULL. Then when you're going back through your Activity after the orientation change you can just call setActivityForTasks(this) to reset the current activity.
The single take-away is just to NULL out any references to anything Activity related in onRetainNonConfigurationInstance

In Gingerbread and lower versions of Android, View.setTag (int key, Object tag) leaks memory. Do not use it. It was fixed in ICS.

Related

Are there any disadvantages to initializing views outside of onCreateView?

Typically the view is an instance variable and initliazed in the onCreateView method as shown with the TextView in the example below
private TextView recipeTitle;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_recipe_detail, container, false);
recipeTitle = (TextView) view.findViewById(R.id.text_title);
return view;
}
public void setRecipeTitle(String recipeTitle) {
this.recipeTitle.setText(recipeTitle);
}
Is there any disadvantage that comes with not making the TextView an instance variable and just accessing it from the needed method as shown below?
public void setRecipeTitle(String recipeTitle) {
if (getView() != null) {
((TextView) getView().findViewById(R.id.text_title)).setText(recipeTitle);
}
}
findViewById isn't the cheapest call you can make. You're encouraged to use it only when necessary.
But as long as you're not calling setRecipeTitle frequently, it shouldn't be too bad of a performance hit.
But it's generally safer to code defensively since users of the custom view may not know it is a more expensive to call it than they're used to.
Summary: Either code pattern will work; the former pattern (saving the findViewById lookup first) would be a slightly better pattern, but not by too much in this specific case.
EDIT: Another reason why it is better to do it in onCreateView is so that you can have simpler null checks. In the latter example, as it is written in the question, setRecipeTitle would throw a null reference exception if it is called before onCreateView is in the Android lifecycle. (A rare case; not sure why you would—on accident during unit testing maybe? Anyway, the Android framework lifecycle is replete with these null reference issues without any compile-time safety. ...sigh.) But if you setup the member variable in onCreateView (in the former example), then you can just check for that one null, instead of fixing the latter example to null check the whole chain of calls (all of which can return null in various circumstances).
You can call findViewById() any time after the parent view is inflated. However, it is quite expensive. If you use your second example and call setRecipeTitle() frequently, you will see a significant hit to your app's performance.

difference between view reference to member variable and local variable

suppose I have an activity, and it contains a TextView. I can initialize the TextView either to a member variable or to a local variable. is there any memory wise difference between these to initialization ?
example :
Activity with local view reference:
public class MainActivity extends Activity{
#OVerride
public void onCreate(Bundle b){
TextView textView = (TextView)findViewById(R.id.my_text_view_id);
}
}
Activity with member view reference:
public class MainActivity extends Activity{
TextView mTextView;
#OVerride
public void onCreate(Bundle b){
mTextView = (TextView)findViewById(R.id.my_text_view_id);
}
}
You should always use the minimal scope. So when you declare a variable you should ask yourself:
"Will I need this variable later in a different function?"
Yes -> Use a member variable
No -> Use a local variable
Edit:
What also to consider is the cost of object creation:
If a function does get called repeatedly it is a good practice to instanatiate an object only once, store it as a member variable and reuse it instead of creating a new instance every time the function gets called.
So the 2nd important question is:
"Will this function get called a lot and do I really need a new instance of the object stored in the variable?"
Yes, often, and no, I can reuse the same object over -> use a member variable. This way the same memory is used and no garbage gets piled up. Use only for large Arrays or objects, it is not needed for simple int vars in loops.
Memory wise global variables are much more prone to memory leaks. The scope any variable depends on its scope. For local variable, the scope is closing braces of the respected method, and variable is automatically garbage collected after the execution of closing braces. Where as global variable will reside in memory until the any object of that class is in memory.
Generally, try to avoid using 'global' variables unless you have good
reason to do .
You can use Local or Global Variable depends upon your Requirement .
Mainly, local variables are that they work within a limited scope means they are declared when a function is called and after ending it the memory taken up by the variable is released.
Finally usage of global variables leads to wastage of memory .

MainActivity does not update after coming back from different activity

I have two activities, a MainActivity and a secondary activity (e.g.: an about screen), then I have an asynctask which updates the UI on the MainActivity. This part works fine, the asynctask updates the UI by calling a method inside the MainActivity which inflates the UI and sets some values. This method also makes all UI components visible.
What doesn't work is, after going to the About screen and back to the MainActivity, the UI is completely blank. I don't understand why this stops working after navigating back from a different activity, which otherwise works fine.
Can someone please advise?
Here's how I draw the UI. This is how I update it from the thread, and it works, until I go to the about screen:
private void DisplayMainContent()
{
Context context = Util.DataStruct.LoadContext();
Log.d("debug", "DisplayMainContent() loaded a context " + context.toString());
RelativeLayout parent = (RelativeLayout)((Activity)context).findViewById(R.id.action_settings);
LayoutInflater li = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
View v = li.inflate(R.layout.activity_main, parent);
TextView version = (TextView) v.findViewById(R.id.latestVerField);
version.setText(Util.DataStruct.GetVal("version"));
}
little story about vanishing data..
advice:
do not use new activity to achieve this - do your about as dialog or dialog fragment
nice example how to show dialog using fragment
don't use static - instead use singleton pattern
Singletons preserve the conventional class approach, and don't require that you use the static keyword everywhere. They may be more demanding to implement at first, but will greatly simplify the architecture of your program. Unlike static classes, we can use singletons as parameters or objects. Also,you can use singletons with interfaces just like any other class.
where i see problem:
this line is all u need to trace yr mistake:
(i think any other fragment of yr code is irrelevant to yr problem)
version.setText(Util.DataStruct.GetVal("version"));
Explanation why:
Util.DataStruct:
should be singleton with valid hard reference to it eg. in Applictation class or any other which life is longer as the activity u use to display data.
are you aware of the existence of garbage collector?
what i'm trying to point out ? why u should avoid STATIC !?
Code(data) flow:
app launched - initializes static class/variables etc
your variables are feed (via async or else way)
your app is closed by ANDROID OS - regardless of the reason
os recreates "stack"
but not yr variables - they are empty/null/defalt - not referenced by values as they shoud in normal code flow
context:
from where do u use yr DisplayMainContent() ? for what u need context there ?
context should be "anchor" for yr app methods which need it. (it's like certain security stuff - "hi this app fragment belong to me i have the right to modify and view contents - so to do any stuuf u pass nearest context u got - from fragment activity dialog widget etc")
if u can use getContext() eg. ("from parent") - dont use any static one
example:
in fragment:
#Nullable
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
Context context = container.getContext();
}
in adapter:
#Override
public View getView(int position, View convertView, ViewGroup parent) {
Context context = parent.getContext();
}
about inflation
- use :
LayoutInflater.from(context).inflate(res,ViewGroup,attachToRoot);
do u use parent in inflation(in fragment doubtless u use in activity doubtful)
for #bcorso:
Do not use more resources than you need.
#TomaszBest sorry, but you really don't know what you're talking
about: Util.DataStruct.GetVal() is calling a static method of the
static class Util.DataStruct, and therefore must return a static class
variable. Static class variables are singletons (only one will ever be
created), and it will not get garbage collected.
An object referenced through a static member variable is strongly referenced until the class is unloaded. A normal ClassLoader never unloads a class, but those used by application do under the right conditions.
If the static field is changed to reference a different object, the original object pointed to by the static field is eligible for GC just like any other object!
The initialization of static variables is covered in Section 2.11 Static Initializers of suns JVM spec. The specification does not define the implementation of Garbage collection - garbage collection rules for static objects will vary depending on your VM.
in sum:
If your class is holding onto this object permanently, it will only be released when the vm exits. Only Classes and interfaces loaded by the bootstrap loader may not be unloaded.

Android non-static inner class memory issue

I have written the following class inside one of my adapters. The class gets a view from the activity inside its constructor:
private class ContactViewHolder {
public TextView tvPrimary;
public TextView tvSecondary;
public ImageView imageViewCheck;
public ContactViewHolder(View v) {
this.tvPrimary = (TextView) v.findViewById(R.id.tv_primary);
this.imageViewCheck = (ImageView) v.findViewById(R.id.iv_check);
v.findViewById(R.id.iv_mm_user);
this.tvSecondary = (TextView) v.findViewById(R.id.tv_secondary);
}
}
Will this lead to a memory leak issue?
It seems it might cause a memory leak, as described in this link, regarding a slightly different issue.
So, to sum up, I want to know:
Will this cause a memory leak issue?
If so, why?
If you define a instance like this:
static ContactViewHolder holder = new ContactViewHolder(view);
It will result in a memory leak.
If you define a non-static instance, it has no memory leak.
The reason of leak is as follows:
No-static inner class will hold a reference of outter class instance, so a static holder will hold a reference of your adapter.
As is known to all, static instance can not be collected by GC, it's a gc root, so your adapter can not be collected until the process is terminated.
Memory leaks occures when you dont know/control the lifecycle of the inner class object. Because its not being collected by the GC then the object the holds him doesn't get collected and so on and that causes a memory leak. Basically if your holder only handles with the ui in the simplest way like you wrote then there is no way that he will "outlive" the adapter that holds him then it will not cause a memory leak.
If for some reason you are implementing onClick listener inside the Holder which open an AsyncTask for example then you dont control the life-cycle of the object because it opens a new Thread and might be doing some operation while the adapter is no longer on the UI and that will cause a memory leak.
Conclusion:
Be very aware of the lifecycle of your objects and who owns them.

Instantiate variables in class instead of onCreate() wrong?

Is something wrong with this construct in Android?
class A extends Activity {
private Object myObject = new Object();
#Override
protected void onCreate(Bundle savedInstanceState) {
//myObject = new Object();
}
}
Because at some point(s) later I get (sometimes, not reproducible yet) exceptions because myObject is null. I don't know if it's because I have to initialize in onCreate.
Edit: Additional details:
The actual class of myObject is List<Object> (Where Object is a domain specific type)
At some point later in the activity I'm storing myObject as a static field of a "Parameter passer" class and starting other Activity (because I'm avoiding to implement Parcelable. If this is good or bad practice should not be discussed here, unless that's causing my error). In the other Activity I pick up myObject. There it's (sometimes) null.
Edit 2: I don't understand why this object becomes null if I'm storing a reference to it as static field of my parameter passer class (a standalone, dedicated class). That's how garbage collection works, right, it just removes when the objects are not referenced anymore. So since I have a static reference this object should not be removed. According to this thoughts, if they are correct, the problem should be somewhere else.
When you start a new activity your old one goes on the block for possible garbage collection (including any classes instantiated in it, including your parameter passer class), so your object is not necessarily going to be available (which is why you see an intermittent failure.).
I see two option:
1) Pass it along in the bundle with your intent that starts the new activity. As you were trying to avoid this, probably not your best choice.
2) Extend the Application class and store the object in there.
EDIT
I think the accepted answer to this SO Question might fix your issue (and explain what is actually happening).
No. That code is just fine. You can create objects in the constructor.
You may want to check a previous question about it Instance variable initialization in java and the section 3.2.4. Field Defaults and Initializers which basically states that the first case:
private Object myObject = new Object();
is identical to an initialization in the class constructor. (NOTICE onCreate is NOT the constructor).
So, myObject should never be null, except in the case the "new Object()" instruction failed, generating an exception.
Isn't this possible your code is changing the contents of myObject later on the code?

Categories

Resources