I have a list view with a couple of image buttons on each row. When you click the list row, it launches a new activity. I have had to build my own tabs because of an issue with the camera layout. The activity that gets launched for result is a map. If I click on my button to launch the image preview (load an image off the SD card) the application returns from the activity back to the listview activity to the result handler to relaunch my new activity which is nothing more than an image widget.

The image preview on the list view is being done with the cursor and ListAdapter. This makes it pretty simple, but I am not sure how I can put a resized image (I.e. Smaller bit size not pixel as the src for the image button on the fly. So I just resized the image that came off the phone camera.

The issue is that I get an out of memory error when it tries to go back and re-launch the 2nd activity.

Is there a way I can build the list adapter easily row by row, where I can resize on the fly (bit wise)?

This would be preferable as I also need to make some changes to the properties of the widgets/elements in each row as I am unable to select a row with touch screen because of focus issue. (I can use roller ball.)

I know I can do an out of band resize and save of my image, but that is not really what I want to do, but some sample code for that would be nice.

As soon as I disabled the image on the list view it worked fine again.

I put the log in a code block to give it scroll bars
– UnkwnTechJan 25 '09 at 11:51

7

I solved this by avoiding Bitmap.decodeStream or decodeFile and using BitmapFactory.decodeFileDescriptor method.
– FraggleAug 19 '11 at 1:56

I Also faced similar issue couple of weeks back and i solved it by scaling down images upto optimal point. I have written complete approach in my blog codingjunkiesforum.wordpress.com/2014/06/12/… and uploaded complete sample project with OOM prone code vs OOM Proof code athttps://github.com/shailendra123/BitmapHandlingDemo
– Shailendra Singh RajawatJun 12 '14 at 10:53

4

The accepted answer on this question is being discussed on meta
– reneSep 7 '15 at 8:44

Read Bitmap Dimensions and Type

The BitmapFactory class provides several decoding methods (decodeByteArray(), decodeFile(), decodeResource(), etc.) for creating a Bitmap from various sources. Choose the most appropriate decode method based on your image data source. These methods attempt to allocate memory for the constructed bitmap and therefore can easily result in an OutOfMemory exception. Each type of decode method has additional signatures that let you specify decoding options via the BitmapFactory.Options class. Setting the inJustDecodeBounds property to true while decoding avoids memory allocation, returning null for the bitmap object but setting outWidth, outHeight and outMimeType. This technique allows you to read the dimensions and type of the image data prior to construction (and memory allocation) of the bitmap.

To avoid java.lang.OutOfMemory exceptions, check the dimensions of a bitmap before decoding it, unless you absolutely trust the source to provide you with predictably sized image data that comfortably fits within the available memory.

Load a scaled down version into Memory

Now that the image dimensions are known, they can be used to decide if the full image should be loaded into memory or if a subsampled version should be loaded instead. Here are some factors to consider:

Estimated memory usage of loading the full image in memory.

The amount of memory you are willing to commit to loading this image given any other memory requirements of your application.

Dimensions of the target ImageView or UI component that the image is to be loaded into.

Screen size and density of the current device.

For example, it’s not worth loading a 1024x768 pixel image into memory if it will eventually be displayed in a 128x96 pixel thumbnail in an ImageView.

To tell the decoder to subsample the image, loading a smaller version into memory, set inSampleSize to true in your BitmapFactory.Options object. For example, an image with resolution 2048x1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512x384. Loading this into memory uses 0.75MB rather than 12MB for the full image (assuming a bitmap configuration of ARGB_8888). Here’s a method to calculate a sample size value that is a power of two based on a target width and height:

This answer (except the information reached through the link) do not offer much of a solution as for an answer. The important parts of the link should be merged into the question.
– FallenAngelSep 7 '15 at 10:02

6

This answer, like the question and the other answers are Community Wiki, so this is something the community can fix by editing, something that does not require moderator intervention.
– Martijn Pieters♦Sep 8 '15 at 18:44

Note that 10 may not be the best value for inSampleSize though, the documentation suggests using powers of 2.
– Mirko N.May 28 '10 at 11:59

65

I'm facing the same problem as Chrispix, but I don't think the solution here really solves the problem, but rather sidesteps it. Changing the sample size reduces the amount of memory used (at the cost of image quality, which is probably okay for an image preview), but it will not prevent the exception if a large enough image stream is decoded, of if multiple image streams are decoded. If I find a better solution (and there may not be one) I'll post an answer here.
– Flynn81Jul 8 '10 at 15:19

2

You only need an appropriate size to match the screen in pixel density, for zooming in and such you can take a sample of the image at a higher density.
– stealthcopterAug 8 '10 at 22:43

2

REQUIRED_SIZE is the new size you want to scale to.
– FedorApr 7 '11 at 0:35

7

this solution helped me but the image quality is terrible. I am using a viewfilpper to display the images any suggestions?
– user1106888Jul 8 '13 at 20:21

I've made a small improvement to Fedor's code. It basically does the same, but without the (in my opinion) ugly while loop and it always results in a power of two. Kudos to Fedor for making the original solution, I was stuck until I found his, and then I was able to make this one :)

You are creating two new FileInputStreams, one for each call to the BitmapFactory.decodeStream(). Don't you have to save a reference to each of them so that they can be closed in a finally block?
– matsevFeb 15 '11 at 8:21

I come from iOS experience and I was frustrated to discover an issue with something so basic as loading and showing an image. After all, everyone that is having this issue is trying to display reasonably sized images. Anyway, here are the two changes that fixed my problem (and made my app very responsive).

1) Every time you do BitmapFactory.decodeXYZ(), make sure to pass in a BitmapFactory.Options with inPurgeable set to true (and preferably with inInputShareable also set to true).

2) NEVER use Bitmap.createBitmap(width, height, Config.ARGB_8888). I mean NEVER! I've never had that thing not raise memory error after few passes. No amount of recycle(), System.gc(), whatever helped. It always raised exception. The one other way that actually works is to have a dummy image in your drawables (or another Bitmap that you decoded using step 1 above), rescale that to whatever you want, then manipulate the resulting Bitmap (such as passing it on to a Canvas for more fun). So, what you should use instead is: Bitmap.createScaledBitmap(srcBitmap, width, height, false). If for whatever reason you MUST use the brute force create method, then at least pass Config.ARGB_4444.

This is almost guaranteed to save you hours if not days. All that talk about scaling the image, etc. does not really work (unless you consider getting wrong size or degraded image a solution).

In Bitmap.createScaledBitmap() call you should probably use true as the flag parameter. Otherwise the quality of the image will not be smooth when scaling up. Check this thread stackoverflow.com/questions/2895065/…
– rOrligMar 1 '12 at 2:26

10

That really is fabulous advice. Wish I could give you an extra +1 for taking Google to task for this amazingly rinky dink bug. I mean... if it's not a bug then the documentation really needs to have some seriously flashing neon signs saying "THIS IS HOW YOU PROCESS PHOTOS", cause I've been struggling with this for 2 years and just now found this post. Great find.
– Genia S.Sep 3 '12 at 9:47

Scaling your images down definitely helps, but this is an important step and what ultimately solved this issue for me. The problem with just scaling your images is if you have a lot of them, or if the source images are very large then you can still run into the same problem. +1 to you Ephraim.
– DaveOct 19 '12 at 20:58

It's a known bug, it's not because of large files. Since Android Caches the Drawables, it's going out of memory after using few images. But I've found an alternate way for it, by skipping the android default cache system.

Solution:
Move the images to "assets" folder and use the following function to get BitmapDrawable:

I had this same issue and solved it by avoiding the BitmapFactory.decodeStream or decodeFile functions and instead used BitmapFactory.decodeFileDescriptor

decodeFileDescriptor looks like it calls different native methods than the decodeStream/decodeFile.

Anyways, what worked was this (note that I added some options as some had above, but that's not what made the difference. What is critical is the call to BitmapFactory.decodeFileDescriptor instead of decodeStream or decodeFile):

I think there is a problem with the native function used in decodeStream/decodeFile. I have confirmed that a different native method is called when using decodeFileDescriptor. Also what I've read is "that Images (Bitmaps) are not allocated in a standard Java way but via native calls; the allocations are done outside of the virtual heap, but are
counted against it!"

same result out of memeory, actually it wont matter which method you are using it depend upon the number of bytes you are holding on to read the data that gives out of memory.
– PiyushMishraOct 11 '11 at 12:32

I think best way to avoid the OutOfMemoryError is to face it and understand it.

I made an app to intentionally cause OutOfMemoryError, and monitor memory usage.

After I've done a lot of experiments with this App, I've got the following conclusions:

I'm gonna talk about SDK versions before Honey Comb first.

Bitmap is stored in native heap, but it will get garbage collected automatically, calling recycle() is needless.

If {VM heap size} + {allocated native heap memory} >= {VM heap size limit for the device}, and you are trying to create bitmap, OOM will be thrown.

NOTICE: VM HEAP SIZE is counted rather than VM ALLOCATED MEMORY.

VM Heap size will never shrink after grown, even if the allocated VM memory is shrinked.

So you have to keep the peak VM memory as low as possible to keep VM Heap Size from growing too big to save available memory for Bitmaps.

Manually call System.gc() is meaningless, the system will call it first before trying to grow the heap size.

Native Heap Size will never shrink too, but it's not counted for OOM, so no need to worry about it.

Then, let's talk about SDK Starts from Honey Comb.

Bitmap is stored in VM heap, Native memory is not counted for OOM.

The condition for OOM is much simpler: {VM heap size} >= {VM heap size limit for the device}.

So you have more available memory to create bitmap with the same heap size limit, OOM is less likely to be thrown.

Here is some of my observations about Garbage Collection and Memory Leak.

You can see it yourself in the App. If an Activity executed an AsyncTask that was still running after the Activity was destroyed, the Activity will not get garbage collected until the AsyncTask finish.

This is because AsyncTask is an instance of an anonymous inner class, it holds a reference of the Activity.

Calling AsyncTask.cancel(true) will not stop the execution if the task is blocked in an IO operation in background thread.

Callbacks are anonymous inner classes too, so if a static instance in your project holds them and do not release them, memory would be leaked.

If you scheduled a repeating or delayed task, for example a Timer, and you do not call cancel() and purge() in onPause(), memory would be leaked.

AsyncTask does not necessarily have to be "an instance of an anonymous inner class", and the same goes for Callbackks. You can create a new public class in it's own file that extends AsyncTask, or even a private static class in the same class. They will not hold any reference to the Activity (unless you give them one of course)
– Simon ForsbergOct 14 '13 at 16:54

Features:

Retains the cache if there is an orientation change, using a singleton

Use one eighth of the assigned application memory to the cache (modify if you want)

Large bitmaps gets scaled (you can define the maximum pixels that you want to allow)

Controls that there is an internet connection available before downloading the bitmaps

Makes sure that you are only instantiating one task per row

If you are flinging the ListView away, it simply won't download the bitmaps between

This does not include:

Disk caching. This should be easy to implement anyway - just point to a different task that grabs the bitmaps from the disk

Sample code:

The images that are being downloaded are images (75x75) from Flickr. However, put whatever image urls you want to be processed, and the application will scale it down if it exceeds the maximum. In this application the urls are simply in a String array.

The LruCache has a good way to deal with bitmaps. However, in this application I put an instance of an LruCache inside another cache class that I created in order to get the application more feasible.

Cache.java's critical stuff (the loadBitmap() method is the most important):

public Cache(int size, int maxWidth, int maxHeight) {
// Into the constructor you add the maximum pixels
// that you want to allow in order to not scale images.
mMaxWidth = maxWidth;
mMaxHeight = maxHeight;
mBitmapCache = new LruCache<String, Bitmap>(size) {
protected int sizeOf(String key, Bitmap b) {
// Assuming that one pixel contains four bytes.
return b.getHeight() * b.getWidth() * 4;
}
};
mCurrentTasks = new ArrayList<String>();
}
/**
* Gets a bitmap from cache.
* If it is not in cache, this method will:
*
* 1: check if the bitmap url is currently being processed in the
* BitmapLoaderTask and cancel if it is already in a task (a control to see
* if it's inside the currentTasks list).
*
* 2: check if an internet connection is available and continue if so.
*
* 3: download the bitmap, scale the bitmap if necessary and put it into
* the memory cache.
*
* 4: Remove the bitmap url from the currentTasks list.
*
* 5: Notify the ListAdapter.
*
* @param mainActivity - Reference to activity object, in order to
* call notifyDataSetChanged() on the ListAdapter.
* @param imageKey - The bitmap url (will be the key).
* @param imageView - The ImageView that should get an
* available bitmap or a placeholder image.
* @param isScrolling - If set to true, we skip executing more tasks since
* the user probably has flinged away the view.
*/
public void loadBitmap(MainActivity mainActivity,
String imageKey, ImageView imageView,
boolean isScrolling) {
final Bitmap bitmap = getBitmapFromCache(imageKey);
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(R.drawable.ic_launcher);
if (!isScrolling && !mCurrentTasks.contains(imageKey) &&
mainActivity.internetIsAvailable()) {
BitmapLoaderTask task = new BitmapLoaderTask(imageKey,
mainActivity.getAdapter());
task.execute();
}
}
}

You shouldn't need to edit anything in the Cache.java file unless you want to implement disk caching.

getView() gets called very often. It's normally not a good idea to download images there if we haven't implemented a check that ensure us that we won't start an infinite amount of threads per row. Cache.java checks whether the rowObject.mBitmapUrl already is in a task and if it is, it won't start another. Therefore, we are most likely not exceeding the work queue restriction from the AsyncTask pool.

Download:

Last words:

I have tested this for a few weeks now, I haven't gotten a single OOM exception yet. I have tested this on the emulator, on my Nexus One and on my Nexus S. I have tested image urls that contain images that were in HD quality. The only bottleneck is that it takes more time to download.

There is only one possible scenario where I can imagine that the OOM will appear, and that is if we download many, really big images, and before they get scaled and put into cache, will simultaneously take up more memory and cause an OOM. But that isn't even an ideal situation anyway and it most likely won't be possible to solve in a more feasible way.

Can you explain what this actually does? Simply telling people to add this doesn't help.
– Stealth RabbiMar 28 '16 at 15:49

This is a very bad solution. Basically you are not trying to fix the problem. Instead asking android system to allocate more heap space for your application. This will have very bad implications on your app like your app consuming lot of battery power as GC has to run through large heap space to clean up memory and also your app performance will be slower.
– PrakashJul 12 '17 at 14:42

then why android is allowing us add this android:largeHeap="true" in our manifest? Now you are challenging Android.
– Himanshu MoriJul 13 '17 at 13:00

It seems that this is a very long running problem, with a lot of differing explanations. I took the advice of the two most common presented answers here, but neither one of these solved my problems of the VM claiming it couldn't afford the bytes to perform the decoding part of the process. After some digging I learned that the real problem here is the decoding process taking away from the NATIVE heap.

That lead me to another discussion thread where I found a couple more solutions to this problem. One is to callSystem.gc(); manually after your image is displayed. But that actually makes your app use MORE memory, in an effort to reduce the native heap. The better solution as of the release of 2.0 (Donut) is to use the BitmapFactory option "inPurgeable". So I simply added o2.inPurgeable=true; just after o2.inSampleSize=scale;.

Now, having said all of this, I am a complete dunce with Java and Android too. So if you think this is a terrible way to solve this problem, you are probably right. ;-) But this has worked wonders for me, and I have found it impossible to run the VM out of heap cache now. The only drawback I can find is that you are trashing your cached drawn image. Which means if you go RIGHT back to that image, you are redrawing it each and every time. In the case of how my application works, that is not really a problem. Your mileage may vary.

I have a much more effective solution which does not need scaling of any sort. Simply decode your bitmap only once and then cache it in a map against its name. Then simply retrieve the bitmap against the name and set it in the ImageView. There is nothing more that needs to be done.

This will work because the actual binary data of the decoded bitmap is not stored within the dalvik VM heap. It is stored externally. So every time you decode a bitmap, it allocates memory outside of VM heap which is never reclaimed by GC

To help you better appreciate this, imagine you have kept ur image in the drawable folder. You just get the image by doing a getResources().getDrwable(R.drawable.). This will NOT decode your image everytime but re-use an already decoded instance everytime you call it. So in essence it is cached.

Now since your image is in a file somewhere (or may even be coming from an external server), it is YOUR responsibility to cache the decoded bitmap instance to be reused any where it is needed.

"and then cache it in a map against its name." How exactly do you cache your images?
– VincentApr 27 '11 at 15:38

3

Have you actually tried this? Even though the pixel data is not actually stored within the Dalvik heap, its size in native memory is reported to the VM and counted against its available memory.
– ErikRJun 10 '11 at 19:45

3

@Vincent I think its not hard to store them in a Map. I would suggest something like HashMap<KEY, Bitmap> map, where the Key can be a String of the source or anything that makes sense for you. Lets assume you take a path as KEY, you store it as map.put(Path, Bitmap) and recieve it through map.get(Path)
– Rafael TJun 29 '11 at 11:58

3

you prob would want to use HashMap<String, SoftReference<Bitmap>> if you are implementing an image Cache otherwise you may run out of memory anyway - also i dont think that "it allocates memory outside of VM heap which is never reclaimed by GC" is true, the memory is reclaimed as i understand just may be a delay, which is what bitmap.recycle() is for, as a hint to reclaim the mem early...
– DoriJul 1 '11 at 8:54

that's all Right but i'm using multiple bitmap for draw circle in OnCreate and activity call 4-5 times so how to clear bitmap and how to remove bitmap and create again for second time when activity 0nCreate..
– ckpatelMay 25 '13 at 5:17

Garbage collection for the native heap is lazier than the VM heap - so you need to be quite aggressive about doing bitmap.recycle and bitmap =null every time you go through an Activity's onPause or onDestroy

To anyone using this: I just fixed a bug: "int scaledBitmapHeight = scaledBitmap.getWidth();" is wrong (obviously. I replaced it with "int scaledBitmapHeight = scaledBitmap.getHeight();"
– PascalFeb 6 '13 at 9:55

None of the answers above worked for me, but I did come up with a horribly ugly workaround that solved the problem. I added a very small, 1x1 pixel image to my project as a resource, and loaded it into my ImageView before calling into garbage collection. I think it might be that the ImageView was not releasing the Bitmap, so GC never picked it up. It's ugly, but it seems to be working for now.

@Mike can you add the complete code of imageloader or else can you give me the link of loading bitmap images. If I use recycle on bitmap all my listview is displayed but all the items are shown blank
– TNRNov 6 '12 at 12:14

@Mike can u tell if i do imageView=null before calling into garbage collection is it good or not ?
– YouddhNov 30 '12 at 5:26

@TNR I think what you're missing here is that bitmap in the above code is the previous already displayed image, you need to recycle that, clear any references to it, make the imageView forget it as well by setting a tiny replacement, gc(); and after all this: load your NEW image to bitmap and display it, the ... in the above code.
– TWiStErRobAug 1 '14 at 20:52

This is wrong. You should always clear your imageView content BEFORE recycling bitmap (instead of while it is actually displayed and being used).
– FindOut_QuranApr 29 '16 at 3:31

In one of my application i need to take picture either from Camera/Gallery. If user click image from Camera(may be 2MP, 5MP or 8MP), image size varies from kBs to MBs. If image size is less(or up to 1-2MB) above code working fine but if i have image of size above 4MB or 5MB then OOM comes in frame :(

then i have worked to solve this issue & finally i've made the below improvement to Fedor's(All Credit to Fedor for making such a nice solution) code :)

I just ran into this issue a couple minutes ago. I solved it by doing a better job at managing my listview adapter. I thought it was an issue with the hundreds of 50x50px images I was using, turns out I was trying to inflate my custom view each time the row was being shown. Simply by testing to see if the row had been inflated I eliminated this error, and I am using hundreds of bitmaps. This is actually for a Spinner, but the base adapter works all the same for a ListView. This simple fix also greatly improved the performance of the adapter.

I can't thank you enough for this! I was chasing the wrong problem before seeing this. Question for you though: Since each or my list rows has a unique name and photo I had to use a convertView array to retain each of the rows' values. I couldn't see how using a single variable would allow you to do so. Am I missing something?
– PeteHJul 29 '13 at 4:43

I've spent the entire day testing these solutions and the only thing that worked for me is the above approaches for getting the image and manually calling the GC, which I know is not supposed to be necessary, but it is the only thing that worked when I put my app under heavy load testing switching between activities. My app has a list of thumbnail images in a listview in (lets say activity A) and when you click on one of those images it takes you to another activity (lets say activity B) that shows a main image for that item. When I would switch back and forth between the two activities, I would eventually get the OOM error and the app would force close.

When I would get half way down the listview it would crash.

Now when I implement the following in activity B, I can go through the entire listview with no issue and keep going and going and going...and its plenty fast.

The Activity States are determined by the OS itself subject to the memory usage for each process and the priority of each process.

You may consider the size and the resolution for each of the bitmap pictures used. I recommend to reduce the size ,resample to lower resolution , refer to the design of galleries (one small picture PNG , and one original picture.)

Generally android device heap size is only 16MB (varies from device/OS see post Heap Sizes), if you are loading the images and it crosses the size of 16MB , it will throw out of memory exception, instead of using the Bitmap for , loading images from SD card or from resources or even from network try to using getImageUri , loading bitmap require more memory , or you can set bitmap to null if your work done with that bitmap.

I set the maximum memory used by this bitmap to be 25% of maximum allocated memory, you may need to adjust this to your needs and make sure this bitmap is cleaned up and don't stay in memory when you've finished using it. Typically I use this code to perform image rotation (source and destination bitmap) so my app needs to load 2 bitmaps in memory at the same time, and 25% gives me a good buffer without running out of memory when performing image rotation.

Thank you for your interest in this question.
Because it has attracted low-quality or spam answers that had to be removed, posting an answer now requires 10 reputation on this site (the association bonus does not count).