Not exactly. I figured this out this morning when my app crashed on an out of memory error, which looked something like this:
/AndroidRuntime(15061): java.lang.RuntimeException: Unable to start
activity ComponentInfo{com.jlptquiz.app/com.jlptquiz.app.StartActivity}:
android.view.InflateException: Binary XML file line #2: Error
inflating class
....
E/AndroidRuntime(15061): Caused by: java.lang.OutOfMemoryError: bitmap
size exceeds VM budget
E/AndroidRuntime(15061): at android.graphics.Bitmap.nativeCreate(Native Method)
Googling turns up this link at SO:
http://stackoverflow.com/questions/1949066/java-lang-outofmemoryerror-bitmap-size-exceeds-vm-budget-android
I've developed an application that uses lots of images on Android.
The app runs once, fills the information on the screen (Layouts, Listviews, Textviews, ImageViews, etc) and user reads the information.
There is no animation, no special effects or anything that can fill the memory. Sometimes the drawables can change. Some are android resources and some are files saved in a folder in the SDCARD.
Then the user quits (the onDestroy method is executed and app stays in memory by the VM ) and then at some point the user enters again.
Each time the user enters to the app, I can see the memory growing more and more until user gets the java.lang.OutOfMemoryError.
So what is the best/correct way to handle many images?
Should I put them in static methods so they are not loaded all the time? Do I have to clean the layout or the images used in the layout in a special way?
Here are some of the answers:
It sounds like you have a memory leak. The problem isn't handling many images, it's that your images aren't getting deallocated when your activity is destroyed.
It's difficult to say why this is without looking at your code. However, this article has some tips that might help:
http://developer.android.com/resources/articles/avoiding-memory-leaks.html
http://stackoverflow.com/questions/1147172/what-android-tools-and-methods-work-best-to-find-memory-resource-leaks
I've got an Android app developed, and I'm at the point of a phone app development where everything seems to be working well and you want to declare victory and ship, but you know there just have to be some memory and resource leaks in there; and there's only 16mb of heap on the Android and its apparently surprisingly easy to leak in an Android app.
I've been looking around and so far have only been able to dig up info on 'hprof' and 'traceview' and neither gets a lot of favorable reviews.
What tools or methods have you come across or developed and care to share maybe in an OS project?
Here are a couple of good blog posts I've come across.
http://android-developers.blogspot.com/2009/02/track-memory-allocations.html
http://android-developers.blogspot.com/2009/01/avoiding-memory-leaks.html
So, it seems it's a fairly common problem. Let's take a look at:
http://android-developers.blogspot.com/2009/02/track-memory-allocations.html
Track memory allocations
Posted by Romain Guy on 12 February 2009 at 10:00 AM
Despite the impressive hardware of the first Android phones (T-Mobile G1 and ADP1) writing efficient mobile applications is not always straightforward. Android applications rely on automatic memory management handled by Dalvik's garbage collector which can sometimes cause performance issues if you are not careful with memory allocations.
In a performance sensitive code path, like the layout or drawing method of a view or the logic code of a game, any allocation comes at a price.
After too many allocations, the garbage collector will kick in and stop your application to let it free some memory. Most of the time, garbage collections happen fast enough for you not to notice. However, if a collection happens while you are scrolling through a list of items or while you are trying to defeat a foe in a game, you may suddenly see a drop in performance/responsiveness of the application. It's not unusual for a garbage collection to take 100 to 200 ms. For comparison, a smooth animation needs to draw each frame in 16 to 33 ms. If the animation is suddenly interrupted for 10 frames, you can be certain that your users will notice.
Most of the time, garbage collection occurs because of tons of small, short-lived objects and some garbage collectors, like generational garbage collectors, can optimize the collection of these objects so that the application does not get interrupted too often. The Android garbage collector is unfortunately not able to perform such optimizations and the creation of short-lived objects in performance critical code paths is thus very costly for your application.
To help you avoid frequent garbage collections, the Android SDK ships with a very useful tool called allocation tracker. This tool is part of DDMS, which you must have already used for debugging purposes. To start using the allocation tracker, you must first launch the standalone version of DDMS, which can be found in the tools/ directory of the SDK. The version of DDMS included in the Eclipse plugin does not offer you ability to use the allocation tracker yet.
Once DDMS is running, simply select your application process and then click the Allocation Tracker tab. In the new view, click Start Tracking and then use your application to make it execute the code paths you want to analyze. When you are ready, click Get Allocations. A list of allocated objects will be shown in the first table. By clicking on a line you can see, in the second table, the stack trace that led to the allocation. Not only you will know what type of object was allocated, but also in which thread, in which class, in which file and at which line. The following screenshot shows the allocations performed by Shelves while scrolling a ListView.
Even though it is not necessary, or sometimes not possible, to remove all allocations for your performance critical code paths. the allocation tracker will help you identify important issues in your code. For instance, a common mistake I have seen in many applications is to create a new Paint object on every draw. Moving the paint into an instance field is a simple fix that helps performance a lot. I highly encourage you to peruse the Android source code to see how we reduce allocations in performance critical code paths. You will also thus discover the APIs Android provide to help you reuse objects.
// Ok, let's see if we can get this allocation tracker working.
Actually, it's now included in eclipses DDMS. But, this proves fruitless - the command line complains about a port number, and the eclipse version just shows me generic objects - nothing I can tie back to the program. But this fellow seemed to get a lot out of it.
If there is one thing showing, there are a lot of objects being created in an object array.
Well, I just glanced at the LogCat in DDMS and I see this:
07-08 10:04:04.920: ERROR/ActivityThread(17012): android.app.IntentReceiverLeaked: Activity com.jlptquiz.app.StartActivity has leaked IntentReceiver com.jlptquiz.app.StartActivity$3@40990378 that was originally registered here. Are you missing a call to unregisterReceiver()?
I don't know if that will make any difference, but I'm checking it for null now:
if (null == receiver) {
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (intent.getAction().equals("my.db.updated")) {
setupJlptLevelDisplay();
}
}
};
registerReceiver(receiver, new IntentFilter("my.db.updated"));
}
But I doubt that's the problem. There's two things I want to investigate; whether I'm keeping reference to activities hanging around, and if I'm stuffing some array that keeps growing and growing unnecessarily. I would also like some more useful profiling tool.
Well, since the problem seems to be related to images and bitmaps, let's refer to the link supplied by the original question:
http://developer.android.com/resources/articles/avoiding-memory-leaks.html
Avoiding Memory Leaks
Android applications are, at least on the T-Mobile G1, limited to 16 MB of heap. It's both a lot of memory for a phone and yet very little for what some developers want to achieve. Even if you do not plan on using all of this memory, you should use as little as possible to let other applications run without getting them killed. The more applications Android can keep in memory, the faster it will be for the user to switch between his apps. As part of my job, I ran into memory leaks issues in Android applications and they are most of the time due to the same mistake:
keeping a long-lived reference to a Context.
// Ok. I'm using a lot of Context references in this app.
On Android, a Context is used for many operations but mostly to load and access resources. This is why all the widgets receive a Context parameter in their constructor. In a regular Android application, you usually have two kinds of Context, Activity and Application. It's usually the first one that the developer passes to classes and methods that need a Context:
// Yes, I often pass an activity around.
@Override
protected void onCreate(Bundle state) {
super.onCreate(state);
TextView label = new TextView(this);
label.setText("Leaks are bad");
setContentView(label);
}
// So, although the onCreate exits, the TextView is now hanging onto label.
This means 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. Therefore, if you leak the Context ("leak" meaning you keep a reference to it thus preventing the GC from collecting it), you leak a lot of memory. Leaking an entire activity can be really easy if you're not careful.
When the screen orientation changes the system will, by default, destroy the current activity and create a new one while preserving its state. In doing so, Android will reload the application's UI from the resources.
// ok, although I don't allow the orientation change - it might be something else
// reloading the UI
Now imagine you wrote an application with a large bitmap that you don't want to load on every rotation. The easiest way to keep it around and not having to reload it on every rotation is to keep in a static field:
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);
}
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.
// Ok, the Drawable is attached to a view here:
label.setBackgroundDrawable(sBackground);
// So, in the setBackgroundDrawable method, it's doing something like
// drawable.setCallBackView(this)
In the code snippet above, this means the drawable has a reference to the TextView
// ok
which itself has a reference to the activity (the Context)
// yes, from TextView label = new TextView(this);
which in turns has references to pretty much anything (depending on your code.)
// Right. So, keeping the drawable as a static means it never gets collected.
// But still - it's only one image, which is going to be loaded anyway.
// Why it a problem keeping that one image around?
This example is one of the simplest cases of leaking the Context and you can see how we worked around it in the Home screen's source code (look for the unbindDrawables() method) by setting the stored drawables' callbacks to null when the activity is destroyed.
// Wow. That's getting into it. Let's look at it.
http://android.git.kernel.org/?p=platform/packages/apps/Launcher.git;a=blob;f=src/com/android/launcher/LauncherModel.java;h=0ef2a806b767142b28b2ff3b37f21f4ca16c355d;hb=cupcake
/**
627 * Remove the callback for the cached drawables or we leak the previous
628 * Home screen on orientation change.
629 */
630 private void unbindDrawables(ArrayList
631 if (desktopItems != null) {
632 final int count = desktopItems.size();
633 for (int i = 0; i < count; i++) {
634 ItemInfo item = desktopItems.get(i);
635 switch (item.itemType) {
636 case LauncherSettings.Favorites.ITEM_TYPE_APPLICATION:
637 case LauncherSettings.Favorites.ITEM_TYPE_SHORTCUT:
638 ((ApplicationInfo)item).icon.setCallback(null);
639 }
640 }
641 }
642 }
Ok, am I doing any caching of drawables? I'll have to check.
There are two easy ways to avoid context-related memory leaks. The most obvious one is to avoid escaping the context outside of its own scope.
// Ok - if avoidable.
The example above showed the case of a static reference but inner classes and their implicit reference to the outer class can be equally dangerous.
// hmm; I'm not doing so many of those; or maybe I am with anonymous inner classes,
// like button listeners.
The second solution is to use the Application context. This context will live as long as your application is alive and does not depend on the activities life cycle.
// Ok, well I use that a lot too. But I keep a lot of stuff in there; maybe not
// as much as activity, but still; like the whole word list. Why do I do that if
// it's in the database? There was a reason. I'll have to check on it.
In summary, to avoid context-related memory leaks, remember the following:
Do not keep long-lived references to a context-activity (a reference to an activity should have the same life cycle as the activity itself)
// ok
If you plan on keeping long-lived objects that need a context, remember the application object. You can obtain it easily by calling Context.getApplicationContext() or Activity.getApplication().
// ok
Avoid non-static inner classes in an activity if you don't control their life cycle, use a static inner class and make a weak reference to the activity inside.
// Need more explanation
The solution to this issue is to use a static inner class with a WeakReference to the outer class, as done in ViewRoot and its W inner class for instance
// Let's check that.
static class W extends IWindow.Stub {
2370 private WeakReference
2371
2372 public W(ViewRoot viewRoot) {
2373 mViewRoot = new WeakReference
2374 }
2375
2376 public void resized(int w, int h, Rect coveredInsets,
2377 Rect visibleInsets, boolean reportDraw) {
2378 final ViewRoot viewRoot = mViewRoot.get();
// WeakReference. What the heck is that? It sounds like some kind of particle
// physics terminoloty.
Here's a blog that gives a reasonable explanation:
http://saeedsiam.blogspot.com/2009/02/weakreference-what-is-that.html
Basically, it seems to indicate that Java can garbage collect something if the reference to it is categorized as weak.
Ok, all this is well and good. But what I'd really like to figure out is how to monitor the memory usage, as it was the SO poster did. Well, android is unix, so let's try say the top command.
Yes, there it is. Here is a line:
20328 0% S 9 105940K 25168K fg app_61 com.jlptquiz.app
The first big number, 105940K, is VSS. The second is RSS. What do they mean? I dunno,
but I think I want them to go down.
Let's kill and restart the app and see what the number is...
22894 0% S 9 107376K 21940K fg app_61 com.jlptquiz.app
Hmm...one number went up, the other down.
What is VSS and RSS?
This link has an interesting display:
http://elinux.org/Android_Memory_Usage
Vss = virtual set size
Rss = resident set size
Pss = proportional set size
Uss = unique set size
In general, the two numbers you want to watch are the Pss and Uss (Vss and Rss are generally worthless, because they don't accurately reflect a process's usage of pages shared with other processes.)
So, I was completely wrong. So, what is the Pss and Uss?
Not given in the default top display. Ok, it's for procrank, and it's permission denied because I haven't unlocked the system yet.
Ah, ok, before I was running DDMS on the wrong system. When I run it on adb shell, of course I get permission denied.
Ah, this might help:
http://stackoverflow.com/questions/2298208/how-to-discover-memory-usage-of-my-application-in-android/2299813#2299813
Still need root. I don't know why I'm so reluctant to root the phone - it's one of the reasons I got a nexus one.
Ok, how about an app which displays memory?
Ok, at last - there's a nice, simple app called "memory usage" of all things, which displays each apps current and max memory usage. With this, and using its refresh button, I can clearly see the memory going up on pretty much of time I go in and out of the app.
So, now that I've identified how to monitor the usage, in the next post we'll tackle the memory leak.
good research..
ReplyDeletekeep it up..