After going through few articles about performance,
Not able to get this statement exactly.
"When a Drawable is attached to a view, the view is set as a callback on the drawable"
Soln: "Setting the stored drawables’ callbacks to null when the activity is destroyed."
What does that mean, e.g.
In my app , I initialize an imageButton in onCreate() like this,
imgButton= (ImageButton) findViewById(R.id.imagebtn);
At later stage, I get an image from an url, get the stream and convert that to drawable, and set image btn like this,
imgButton.setImageDrawable(drawable);
According to the above statement, when I am exiting my app, say in onDestroy()
I have to set stored drawables’ callbacks to null, not able to understand this part ! In this simple case what I have to set as null ?
I am using Android 2.2 Froyo, whether this technique is required, or not necessary.
You would have to do this only if you kept the drawable as a static field somewhere, or in a cache of some sort. In this particular situation, there's no reason to set the callback to null.
Here is exactly what was the case in example you cited:
Phone orientation has been changed, and this should mean that old activity should be "dumped" and new one created
If you have stored reference to bitmap as a static field, it has reference to old activity that was supposed to be dumped (drawable has reference to TextView, view has reference to activity)
New activity is created, but your drawable still has the reference to old one, so old one can't be dumped.
Of course, all this is right if you store drawable as static like in cited example:
private static Drawable sBackground;
Related
I have an object that overwrites the Application object. In it, I have a member variable which is a LongSparseArray where the key is some identifier of type long and the value is an object with 2 member variables: a Bitmap and a long which is used as a timestamp.
This is my global image cache. Occasionally, a function is ran that looks at the timestamps and ages things that are over an hour old.
By "age" I mean that it removes that entire entry from the LongSparseArray.
Here is my question:
Suppose I have an Activity with a ListView. Each row in the ListView has an ImageView that is populated with an image from the cache.
Bitmap image = ((MyApp)getApplicationContext()).getImage(id);
holder.imgImage.setImageBitmap(image);
Now, suppose the user clicks some button which takes them to a new Activity. While on this new Activity, the image previously assigned to a row in the ListView in the previous Activity ages.
So, to recap, that Bitmap key/value entry now no longer exists in the global LongSparseArray.
Is that Bitmap really able to be reclaimed by Java? Isn't it still being referred to by the ImageView in the ListView of the previous Activity? Assuming, of course, that Android hasn't reclaimed the memory used by that Activity.
The reason I'm asking about this is my previous aging function would also call .Recycle() on the Bitmap. In this scenario, when the user hit the back button and returned to the previous Activity which was using that Bitmap, the application would crash, presumably because that Bitmap was not only missing from the cache, but also from memory. So I just removed the .Recycle() call.
By the way, once the Bitmap is removed from the cache, and an object with that id shows up on screen again, the application will download the Bitmap again and place it in the cache. If the previous one stayed in memory, you could see how this would present a problem.
Also, does anyone have any ideas for a more effective solution?
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
There are 2 Activities which use this image caching. One is a search screen that displays a list of items (and their images) after the user performs a search. The other is a list of those items the user has then selected to keep.
Issue: Once recycle() method is called on a bitmap, the bitmap should never be used again. If an attempt is made to draw the bitmap, then an exception will be thrown. From docs:
You should use recycle() only when you are sure that the bitmap is no
longer being used. If you call recycle() and later attempt to draw the
bitmap, you will get the error: "Canvas: trying to use a recycled
bitmap".
In this specific case, you have recycled the bitmap, but the ListView item's ImageView has a strong reference to the bitmap. When you return to the Activity, the ListView item attempts to draw the bitmap, hence the exception is thrown.
Bitmap memory management: Prior to Android 2.3.3, the backing pixel data of a bitmap was stored in native memory and bitmap itself in Dalvik memory. Hence to release the native memory, recycle method has to be called.
Here is Bitmap.recycle function definition:
public void recycle() {
if (!mRecycled) {
if (nativeRecycle(mNativeBitmap)) {
// return value indicates whether native pixel object was actually recycled.
// false indicates that it is still in use at the native level and these
// objects should not be collected now. They will be collected later when the
// Bitmap itself is collected.
mBuffer = null;
mNinePatchChunk = null;
}
mRecycled = true;
}
}
Post Android 3.0, the backing pixel data is also stored in Dalvik memory. When the bitmap is no longer required, we need to ensure we don't hold any strong reference to the bitmap, so that it is garbage collected.
Solution: If you are still supporting Android 2.3.3 and lower version, you still need to use recycle to release the bitmap.
You can use reference counting to track whether the bitmap is currently being referenced by the ListView item, so that even it is aged, you don't call recycle on the bitmap.
ListView adapater's getView method is the place where the bitmap is assigned to the ImageView. Here you increment the reference count. You can attach setRecyclerListener to the ListView to know whenever the listview item is put into recycle bin. This is the place you would decrement the reference count of the bitmap. The aging function need to recycle the bitmap only if the reference count is zero.
You can also consider using LruCache for caching, as mentioned in docs.
setDrawingCacheEnabled: By calling this method with true param, the next call to getDrawingCache will draw the view to a bitmap. The bitmap version of view can be rendered on to the screen. Since it is just a bitmap, we cannot interact with it as done with an actual view. Couple of use cases are:
When ListView is being scrolled, the bitmap of the displayed items view is captured and rendered. So that the views being scrolled don't undergo measure and layout pass.
View hierarchy feature in DDMS.
Is that Bitmap really able to be reclaimed by Java? Isn't it still
being referred to by the ImageView in the ListView of the previous
Activity? Assuming, of course, that Android hasn't reclaimed the
memory used by that Activity.
The Bitmap is stilled used in the ListView (a strong reference) so dalvik can't reclaim its memory.
Apparently you can't call recycle on the Bitmap or bad things will happen(app crash, e.g.).
What would happen if I set myImageView.setDrawingCacheEnabled(false);?
If you disable drawing cache, every time your view needs to be redrawn, the onDraw method will be called.I'm not very familiar with ImageView , you can go and read its source for a deep understanding.
(Note: the usage of drawing cache is different when hardware accerleration is enabled/disabled, here I just assume you're using software rendering).
For the solution, you can try the following:
when the Bitmap cache become stale, you remove it from the cache array(and then you app will try to get a new one, I think).
In ListView.getView, you can check whether currently used Bitmap ages. It should be easy because you know the timestamp when you call setImageBitmap the first time and the latest timestamp. If they are not same, you call setImageBitmap again using the new Bitmap and the old one will be reclaimed.
Wish this helps.
Regarding, "Also, does anyone have any ideas for a more effective solution?"
The Picasso library would help solve the problems you are facing http://square.github.io/picasso/
Picasso is "A powerful image downloading and caching library for Android"
"Many common pitfalls of image loading on Android are handled automatically by Picasso:
Handling ImageView recycling and download cancelation in an adapter.
Automatic memory and disk caching."
First I create a new bitmap
Bitmap image = BitmapFactory.decodeResource(Game.panel.context.getResources(),R.drawable.dinofront)
Then I stretch the image, after this I check if the bitmap is null, it is not
image = Bitmap.createScaledBitmap(image, Game.tileWidth, Game.tileHeight, false);
Later in the draw() method, before I do anything with the bitmap, I check again and it is now null. Does anyone have any idea why this is happening? Is there anything special I have to do that I am missing?
Full classes: http://pastebin.com/t95MVvv0
I can think of two possible reasons:
It may simply be that the image variable may be changed by other part of your code
Thread synchronization - remember that codes may not be executed in the order specified in the Java source code, but can be reorder as the JVM deemed fit, which includes assigning the instance variable before the constructor is run. This is especially likely when you are doing extremely time consuming Bitmap op within the constructor, and I assume your object creation code is on a different thread than your drawing code. To check for this possibility you can declare a volatile boolean variable that is only set to true when the constructor is finished, and check its value within your draw() method
Wow I feel stupid. Because there is the image for the class and the image that is brought in with the constructor when I call image = Bitmap.create... it is only setting the constructor's image not this.image so if I change it to this.image =... it all works fine
Whenever we need a reference to the widget, we uses findByViewById.
When we are referring the widget lots of time in the code of the same Activity class, we can follow either of the approach:
Call findViewById every time in Activity lifecycle.
Get it first time, store the reference as a private instance variable of the Activity class.
Which approach is beter? What would be pros and cons of each approach in terms of performance and memory. Please help.
EDIT: If we move to new activity from A to B, we do not finish A as we want to open A on pressing back. In this scenario how to approach above problem? Please help.
Both approaches have their risks. In general, you should call findViewById() the less times you can, by the other hand, storing a reference on the Activity class may lead to memory leaks. It depends so much on what you want to do, how much times are you calling it and basing on it choose one of the approaches. For that, you'll need to analyze your code and if you're not clear about which is better, just try both and choose the "less bad", but generally the first approach is worse than the second one because you know you'll always have to find across ALL elements you've defined an id.
Most developers use method 2, mostly because its more effective. If your layout is complicated then findViewById must traverse its tree to find given widget which takes time. In list views you mostly use ViewHolder pattern which allows you to store references to list item widgets. Since lists are redrawn very ofthen this greatly speeds up its rendering.
Storing widgets in private references is quite safe, those references gets invalidated on configuration changes, but your activity is also destroyed then.
The second possibility is clearly the better.
findViewById iterates through the whole view hierachy, which of course costs much more time than a reference.
Dianne Hackborn (Android engineer) gave some details about the topic here: https://groups.google.com/forum/#!topic/android-developers/_22Z90dshoM
Accessing a member variable is always faster than any function call. The used space for that variable is insignificant.
By the way: The code looks much cleaner!
You should decide this according to your purpose. Holding an object for your views are faster than getting your view with an activity method. But this also means that you are using a memory for your reference and it can cause a memory leak.
I may be wrong since i am new to Android, but i prefer storing a a variable;
it's less coding to write.
for example: if you have to access an imageview that is nested in layouts how would you wish to access it and get its tag.
Access 1:
public Integer getTag(){
FrameLayout frame1 = (FrameLayout) findViewById(R.id.frame_1);
LinearLayout linear3 = (LinearLayout) frame1.findViewById(R.id.linear_3);
ImageView imgView = (ImageView) linear3.findViewById(R.id.myImg);
return Integer.valueOf( imgView.getTag().ToString());
}
Access 2:
private ImageView myImageView;
#Override
public void onCreate( Bundle savedInstanceState){
//set access to variable
}
public Integer getTag(){
//return Integer.valueOf( myImageView.getTag().ToString());
//can be written
Integer mTag = Integer.valueOf(myImageView.getTag().ToString());
return mTag;
}
Things were going well until I switched off the screen lock on my device, then things started going wrong intermittently.
I've managed to track the issue down and have some workarounds in mind BUT I would like to know if there is 'best practice' for avoiding or removing the issue.
The issue:
I have an application which changes images based on the application status.
The images are not huge but quite large (231k~) and are stored as resources.
After several screen rotations (I counted 27 with a project using a single ImageView), loading the images fails with Exception of type 'Java.Lang.OutOfMemoryError'
Stripped down to the barest project, the following demonstrates the problem:
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
//get a reference to the ImageView
var imageView = FindViewById<ImageView>(Resource.Id.imageView1);
imageView.SetImageBitmap( Android.Graphics.BitmapFactory.DecodeResource( this.Resources, Resource.Drawable.Ready) );
}
The above code is the only method I used to reproduce the issue.
Whilst attempting to resolve, I extended the example so that imageView was released in OnDestry:
protected override void OnDestroy ()
{
base.OnDestroy ();
imageView.SetImageBitmap( null );
imageView.DestroyDrawingCache();
imageView.Dispose();
}
This made no difference unless I added GC.Collect() which I don't want to do.
The best workaround I've currently thought of so far would be to modify the code as follows:
static Bitmap _ready = null;
private Bitmap GetReadyImage {
get {
if (_ready == null) {
_ready = Android.Graphics.BitmapFactory.DecodeResource (this.Resources, Resource.Drawable.Ready);
}
return _ready;
}
}
protected override void OnCreate (Bundle bundle)
{
base.OnCreate (bundle);
// Set our view from the "main" layout resource
SetContentView (Resource.Layout.Main);
//get a reference to the ImageView
imageView = FindViewById<ImageView>(Resource.Id.imageView1);
imageView.SetImageBitmap( GetReadyImage );
}
This relies upon a static reference to each Bitmap and a property accessor for each.
I could even write a method which stores the images in a static List to save writing property accessors for each different property/variable.
I could perhaps add the flags ConfigurationChanges = ConfigChanges.Orientation | ConfigChanges.ScreenSize |ConfigChanges.KeyboardHidden) but this would break the normal Activity lifecycle which I've read isn't best practice?
I find it strange that having scoured the web, I've not yest encountered similar issues or examples. I'm left wondering how most others deal with this?
Any thoughts or comments are much appreciated.
I can only approach this problem from a truly native perspective, as I have not worked directly with the Mono framework.
The described symptoms are 100% indicative of a memory leak in the Activity, but the code shows no real evidence. If you can truly produce the issue with a project containing only one Activity and those four lines of code, it sounds to me like it is perhaps a framework bug that ought to be filed with Xamarin. Have you attempted to create the same simple project in pure Java to see how the results fare on the same device/emulator you are using? It would also be interesting to know if the issue is localized to a specific version of Android. I have never seen this particular behavior before in a native application project.
The awkward part is your statement that forcing a garbage collection makes the problem go away. You're right, you shouldn't have to do that, but a true memory leak (i.e. an unreleased reference) would still persist even if you hit the GC several times. The Android paradigm of destroying and re-creating the Activity on each rotation is such that even if the old Activity lived for awhile, when memory was tight it (and all its references) would quickly be collected to make room for a new instance. If this is not happening, and each Activity is living on past even the system triggered GC passes, perhaps there is a stuck reference in the native code generated by Mono.
Interestingly enough, technically your workaround actually does introduce a true leak, by attaching the Bitmap to a static field that is never cleared. However, I agree that in comparison it seems like a more efficient move. A simpler workaround might also be to code your Activity to manually handle configuration changes (I don't know if Mono is different, but this is accomplished by adding android:configChanges="orientation" to the manifest file). This will keep your Activity from being recreated on each rotation, but it may also require you to reload your view hierarchy if you have different layouts for landscape and portrait. However, even if you have to do this the Acitivity instance will be the same you can safely save the Bitmap without resorting to a static field.
However, if you cannot reproduce the problem with the same project in a native Java project, I would report a Mono bug.
Hard to see without the entire code but it obviously sounds like you have a memory leak. Screen rotation (due to the destroying/creation of the activity) is known to cause these. You might want to have a read at this article by Romain Guy as well as this talk from last year's IO.
My activity have several static bitmap arrays,but sometimes those static bitmap is gone when i open other activity, such as load photo using the intent below:
My Code
Intent intent = newIntent(Intent.ACTION_PICK,android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
startActivityForResult(intent, ACTIVITY_SELECT_IMAGE);
When i stay too long in intent which i call to pick image, all my static bitmap array is gone.. i can't use final static because i change those bitmap on run time..
How do i prevent this? Thanks.
Android offers no guarantees on static references like that. The reason that it sometimes works and sometimes not is basically because sometimes the garbage collector(GC) has set your array to null and sometimes not. The GC will not set your reference to null if you activity is active, but it will if it has to when your activity is inactive, e.g. not visible.
You need to add a check for null in the onResume() method and make a new Bitmap array if necessary. As for the bitmaps themselves that are stored in the array, you need to store them in memory. Check the following article and read about saving cache files via the getCacheDir() method. That is basically what you need since the data does not need to be stored persistently across multiple sessions.
http://developer.android.com/guide/topics/data/data-storage.html