a very old link about memory leaks talks about a drawable that has a reference to a view (link here) .
i have some simple questions regarding it:
why does a drawable have a reference to a view ?
what does the drawable do to the view ?
does it have a reference to all of the views that use it?
do all kinds of drawable have references to views ?
I was reading Romain Guy's article too. The website/ blog is now gone, Wayback link.
Drawable's have a private field (mCallback) which refers to an instance of a class which implements the Drawable.Callback interface, documented here. View implements this interface, and this callback reference is automatically set by the system when view.setBackground is called.
public class View extends Object implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {}
Why set this field? It's usage is an implementation detail of Drawable, so it's difficult to know what exactly its usage is. "The drawable uses this interface to schedule/ execute animation changes." is all I could get from the docs. I guess the main reason is to call void scheduleDrawable(Drawable, Runnable, TimeToExecuteMeasuredSinceAppLaunch) doc.
Implement this interface if you want to create an animated drawable that extends Drawable. Upon retrieving a drawable, use Drawable#setCallback(android.graphics.drawable.Drawable.Callback) to supply your implementation of the interface to the drawable; it uses this interface to schedule and execute animation changes.
So to answer your questions specifically
why does a drawable have a reference to a view ? To call the interface (scheduleDrawable and other interface methods) to animate itself. And also, "to schedule and execute animation changes."
what does the drawable do to the view ? The drawable calls those methods in the interface.
does it have a reference to all of the views that use it? The Drawable has 1 callback (so only 1 view can be used), and it can set it with setCallback documented here.
do all kinds of drawable have references to views ? If you set the setCallback, yes. It doesn't have to be "animated" to have the reference, because this (setCallback) is automatically done with View.setBackground(Drawable) and ImageView. according to the Drawable docs.
Finally, I find his post confusing, as he glossed over this detail which was the fundamental cause of the memory leak (the Drawable.Callback interface and more importantly, the mCallback field). In the end, the callback in Drawable is stored as private WeakReference<Callback> mCallback = null;. It is a weak reference which should not cause the memory leak he says. Maybe this was a change to Android after his blog post.
EDIT: Aha! It was Romain who then changed it in 2010:
1.why does a drawable have a reference to a view ?
A drawable have a reference to a View due to allow it the intercept the view state , suppose that you have a selector-drawable that when view mode change in example pressed, focus, disable change it background
Related
In this offical blog site, I read below example of memory leak.
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
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);
}
To quote the original post "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.)"
I do not understand this part. When the activity is recreated, the onCreate() method will be executed, the static Drawable object sBackground will be attached to the new TextView in the second activity. Meaning that the object sBackground will reference to the new textview instead of the old textview in the first activity, leaving the first activity un-referenced.
Can anybody tell me where is wrong in my reasoning? Thanks in advance~~
Oops, it seems this thread is a duplicate, someone asked exactly the same thing here
Understanding memory leaks in Android application
Sorry for my carelessness.
As far as I know after checking the source of View class, when a Drawable is attached to a view, the view is set as a callback on the drawable. Beside, the view also keep a reference to this Drawable. Please see this link http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/view/View.java#16292.
And because static variable has long lifetime, then every time you recreate activity & set a Drawable to its view, the activity and its Context also keep a reference to the static Drawable. Then take memories as long as variable does.
TL;DR: Is there anything in com.android.layoutlib.bridge.android.BridgeContext that can substitute for Activity#findViewById(...)? I've looked at the source, but I can't find anything.
When running on a real device, an attached view's #getContext() returns the Activity. The view can cast it and call #findViewById(...) to obtain a reference to some other view.
But when running in a WYSIWYG editor, #getContext() returns an instance of a different class. I'm getting com.android.layoutlib.bridge.android.BridgeContext. This class isn't part of the public API, so I'm planning to access it via reflection and degrade gracefully if the implementation changes.
If you're wondering why my view wants a reference to another view... I've created a view that appears to have a hole in it. It works by delegating its drawing to another view. If the view with the hole is placed on top of other views, then it appears to punch a hole through any views beneath it, all the way down to the view it's using for drawing. It works perfectly on a real device, but it would be nice to have it also work in the WYSIWYG editor.
It's bad to assume that View.getContext(), or any other platform method that returns Context, can be cast directly to more concrete classes, like Activity. There exist classes like ContextThemeWrapper which can easily destroy your assumption.
I would recommend restructuring what you are doing so that you have a parent layout that can act as an intermediary for the hole-y View and what's below it.
Or you could have a setter which would provide the View for you.
A last option is to call View.getParent() a bunch of times to get the root View and call findViewById() on that:
ViewParent parent;
while(getParent() != null) {
parent = getParent();
}
View root = (View) parent;
root.findViewById(R.id.my_view);
BTW, BridgeContext is used in the WYSIWYG in place of Activity because it only mocks the Android View/Layout/Rendering system, it doesn't emulate it completely. This can be seen in other ways like how it renders shadows or shape drawable rounded corners.
I awarded the bounty to dandc87 because his answer led me to the solution. However, the code snippet in his answer crashes with a ClassCastException because the root ViewParent is not a View. The mods keep rejecting my edits, so here's the complete and correct solution:
private View findPeerById(int resId) {
View root = this;
while(root.getParent() instanceof View) {
root = (View) root.getParent();
}
return root.findViewById(resId);
}
Background:
I'm looking for a way to extend the GridView I need implement a col- and row-span like in HTML. I have hunderds of elements so I cannot use e.g. a TableLayout. However that would be too localized. So far I reduce the question to how to extend the GridView?
What I have tried and where I have failed:
My first approach was to look which methods I need to replace and I found some private methods which cannot be overriden. So I tried to copy the hole source code from the GridView to a new class called CustomGrid. That was a terrible failure since I cannot access the com.android.internal.R class.
Then I dropped that idea and looked if I can normal extend the class and replace all the private methods with custom copies. I took a pen and build a huge tree where all methods are used.
After I found all references I tried to extend the class normal and I added the first method:
private View fillDown(int pos, int nextTop) {
View selectedView = null;
int end = (mBottom - mTop);
if((mGroupFlags & CLIP_TO_PADDING_MASK) == CLIP_TO_PADDING_MASK) {
end -= mListPadding.bottom;
}
// ...
Here is my next problem that member variables mBottom and mTop are unknown. I digged a little through the sources and found them finally in the View class, but unfortunately they are hidden.
/**
* The distance in pixels from the top edge of this view's parent
* to the bottom edge of this view.
* {#hide}
*/
#ViewDebug.ExportedProperty(category = "layout")
protected int mBottom;
Question:
How can I extend the GridView without hitting that lamentations and without the usage of massive reflection? I mean it seems to be impossible to write that as a pure custom control.
How can I extend the GridView without hiting that limentations and without the usage of massive reflection?
Most likely, you don't. You copy the code into your project and modify to suit, including all requisite superclasses (up to ViewGroup). I will be stunned if you can achieve your aims by a simple subclass of GridView. You may even have to completely write your desired widget from scratch.
That was a terrible failior due I cannot access the com.android.internal.R class.
You will also need to copy over relevant resources, then fix up R references to point to your own resources.
but unforcantly they are hidden.
You find other ways of getting this data. mBottom, for example, can be changed to getBottom().
Is it crucial for performance to have ViewHolder as static in a ViewHolder pattern?
A ViewHolder object stores each of the component views inside the tag
field of the Layout, so you can immediately access them without the
need to look them up repeatedly. First, you need to create a class to
hold your exact set of views. For example:
static class ViewHolder {
TextView text;
TextView timestamp;
ImageView icon;
ProgressBar progress;
int position;
}
It's not crucial for performance, it is about using. If ViewHolder class will not be static - you have to provide instance of parent class:
No enclosing instance of type Type is accessible.
Must qualify the allocation with an enclosing instance of type Type
(e.g. x.new A() where x is an instance of Type).
Edit: misunderstood the question -- it seems you are asking specifically about making it static. That shouldn't be crucial for performance, but the idea is every bit helps.
Final edit here: Static nested class in Java, why?
====
Orig answer below:
It's very nice to squeeze performance out of a heavy ListView (or some other type of recycled AdapterView). However the best way to tell would be to profile the performance somehow.
Also at Google IO 2010 they recommend this method:
http://www.youtube.com/watch?v=wDBM6wVEO70
Edit:
Also here's a link to traceview to profile the performance, though I'm unsure how well it works.
http://developer.android.com/tools/debugging/debugging-tracing.html
It's not mandatory to do that. But when you use to do like this, you are using the views again when your adapter view is null. You are creating a view and assigning values to the view part, and tag the whole view using static class ViewHolder. So when you come back and view is not null, then visible part will come from to get the tag. This is how you will create less object as well less work load on adapter.
two days ago i noticed something. I have a spinner over a map activity. In the OnCreate() method of the activity i populate the spinner with data. After that i start the heap analyzer in DDMS i begin to open/close the spinner. I noticed the VM allocate memory when i open the spinner items, but when i close it, the VM do no free this memory. I've tried to start the GC, but the memory is still allocated. i did this 20 times one by one and the allocated memory increased from 3.5MB to 7MB. What is wrong? I found an issue in google groups, but they haven't answered yet.
Spinner memory leak
I rewrite all my code in the spinner adapter, but the issue still remains.
I read some advices in this topic
Avoid memory leaks
There is something i did not get:
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.)
What does it mean? If i have a textview and set it a drawable object (i noticed the drawable is static), the textview object has a reference to the drawable object and the drawable object has a reference to the view too? If this is true, they become undestroyable by the GC because they both have references to each other? What is this back-reference (callbacks) dependencе between the objects?
Sorry I can't help you on your Spinner problem but I can have a try on the second part:
Romain Guy post on android developer blog explain two important things.
First:
When you create a View (TextView, ImageView...) you must not create it with the activity Context
// DO NOT DO THIS
TextView label = new TextView(this);
Otherwise the View get a reference to your activity and will never be deallocated.
Instead, when you create a View programatically, you have to use the application context:
TextView label = new TextView(getApplicationContext());
Second:
When you link a Drawable to an View, it keeps a callback on your activity via the Context. If you leave it, it will leak memory when your activity is destroy.
The thing to do to avoid that is to "set stored drawables' callbacks to null when the activity is destroyed" so for example whith an ImageView:
protected void onDestroy() {
imageView.getDrawable().setCallback(null);
super.onDestroy();
}
You have to do the same for the background drawable...
Hope it helps.