Strange OutOfMemory issue while loading an image to a Bitmap object

Questions : Strange OutOfMemory issue while loading an image to a Bitmap object

I have a ListView with a couple of image buttons on each row. When the user clicks 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 the 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 ListView 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 OutOfMemoryError 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 (bitwise)?

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 the touch screen because of the focus issue. (I can use rollerball.)

  • I know I can do an out of band resize and save 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 ListView it worked fine again.

FYI: This is how I was doing it:

String[] from = new String[] { DBHelper.KEY_BUSINESSNAME, DBHelper.KEY_ADDRESS, DBHelper.KEY_CITY, DBHelper.KEY_GPSLONG, DBHelper.KEY_GPSLAT, DBHelper.KEY_IMAGEFILENAME + ""}; int[] to = new int[] { R.id.businessname, R.id.address, R.id.city, R.id.gpslong, R.id.gpslat, R.id.imagefilename }; notes = new SimpleCursorAdapter(this, R.layout.notes_row, c, from, to); setListAdapter(notes); 

Where R.id.imagefilename is a ButtonImage.

Here is my LogCat:

01-25 05:05:49.877: ERROR/dalvikvm-heap(3896): 6291456-byte external allocation too large for this process. 01-25 05:05:49.877: ERROR/(3896): VM wont let us allocate 6291456 bytes 01-25 05:05:49.877: ERROR/AndroidRuntime(3896): Uncaught handler: thread main exiting due to uncaught exception 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): java.lang.OutOfMemoryError: bitmap size exceeds VM budget 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.nativeDecodeStream(Native Method) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:304) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:149) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.BitmapFactory.decodeFile(BitmapFactory.java:174) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.graphics.drawable.Drawable.createFromPath(Drawable.java:729) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.resolveUri(ImageView.java:484) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ImageView.setImageURI(ImageView.java:281) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.setViewImage(SimpleCursorAdapter.java:183) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.SimpleCursorAdapter.bindView(SimpleCursorAdapter.java:129) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.CursorAdapter.getView(CursorAdapter.java:150) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.obtainView(AbsListView.java:1057) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.makeAndAddView(ListView.java:1616) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.fillSpecific(ListView.java:1177) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.ListView.layoutChildren(ListView.java:1454) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.AbsListView.onLayout(AbsListView.java:937) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutHorizontal(LinearLayout.java:1108) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:922) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1119) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.layoutVertical(LinearLayout.java:999) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.LinearLayout.onLayout(LinearLayout.java:920) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.widget.FrameLayout.onLayout(FrameLayout.java:294) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.View.layout(View.java:5611) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.performTraversals(ViewRoot.java:771) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.view.ViewRoot.handleMessage(ViewRoot.java:1103) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Handler.dispatchMessage(Handler.java:88) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.os.Looper.loop(Looper.java:123) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at android.app.ActivityThread.main(ActivityThread.java:3742) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invokeNative(Native Method) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at java.lang.reflect.Method.invoke(Method.java:515) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:739) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:497) 01-25 05:05:49.917: ERROR/AndroidRuntime(3896): at dalvik.system.NativeStart.main(Native Method) 01-25 05:10:01.127: ERROR/AndroidRuntime(3943): ERROR: thread attach failed 

I also have a new error when displaying an image:

22:13:18.594: DEBUG/skia(4204): xxxxxxxxxxx jpeg error 20 Improper call to JPEG library in state %d 22:13:18.604: INFO/System.out(4204): resolveUri failed on bad bitmap uri: 22:13:18.694: ERROR/dalvikvm-heap(4204): 6291456-byte external allocation too large for this process. 22:13:18.694: ERROR/(4204): VM won't let us allocate 6291456 bytes 22:13:18.694: DEBUG/skia(4204): xxxxxxxxxxxxxxxxxxxx allocPixelRef failed 

Total Answers: 44 Answers 44


Popular Answers:

  1. The Android Training class, “Displaying Bitmaps Efficiently“, offers some great information for understanding and dealing with the exception `java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps.


    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 the construction (and memory allocation) of the bitmap.

    BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; 

    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 1024×768 pixel image into memory if it will eventually be displayed in a 128×96 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 2048×1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512×384. 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:

    public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } 

    Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize documentation.

    To use this method, first decode with inJustDecodeBounds set to true, pass the options through and then decode again using the new inSampleSizevalue andinJustDecodeBoundsset tofalse`:

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } 

    This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100×100 pixel thumbnail, as shown in the following example code:

    mImageView.setImageBitmap(  decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); 

    You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode* method as needed.

  2. The Android Training class, “Displaying Bitmaps Efficiently“, offers some great information for understanding and dealing with the exception `java.lang.OutOfMemoryError: bitmap size exceeds VM budget when loading Bitmaps.


    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 the construction (and memory allocation) of the bitmap.

    BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; 

    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 1024×768 pixel image into memory if it will eventually be displayed in a 128×96 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 2048×1536 that is decoded with an inSampleSize of 4 produces a bitmap of approximately 512×384. 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:

    public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; } 

    Note: A power of two value is calculated because the decoder uses a final value by rounding down to the nearest power of two, as per the inSampleSize documentation.

    To use this method, first decode with inJustDecodeBounds set to true, pass the options through and then decode again using the new inSampleSizevalue andinJustDecodeBoundsset tofalse`:

    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } 

    This method makes it easy to load a bitmap of arbitrarily large size into an ImageView that displays a 100×100 pixel thumbnail, as shown in the following example code:

    mImageView.setImageBitmap(  decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100)); 

    You can follow a similar process to decode bitmaps from other sources, by substituting the appropriate BitmapFactory.decode* method as needed.

  3. 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 🙂

     private Bitmap decodeFile(File f){ Bitmap b = null; //Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(f); BitmapFactory.decodeStream(fis, null, o); fis.close(); int scale = 1; if (o.outHeight > IMAGE_MAX_SIZE || o.outWidth > IMAGE_MAX_SIZE) { scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / (double) Math.max(o.outHeight, o.outWidth)) / Math.log(0.5))); } //Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; fis = new FileInputStream(f); b = BitmapFactory.decodeStream(fis, null, o2); fis.close(); return b; } 
  4. 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).

  5. 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:

    public static Drawable getAssetImage(Context context, String filename) throws IOException { AssetManager assets = context.getResources().getAssets(); InputStream buffer = new BufferedInputStream((assets.open("drawable/" + filename + ".png"))); Bitmap bitmap = BitmapFactory.decodeStream(buffer); return new BitmapDrawable(context.getResources(), bitmap); } 
  6. 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):

    private void showImage(String path) { Log.i("showImage","loading:"+path); BitmapFactory.Options bfOptions=new BitmapFactory.Options(); bfOptions.inDither=false; //Disable Dithering mode bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future bfOptions.inTempStorage=new byte[32 * 1024]; File file=new File(path); FileInputStream fs=null; try { fs = new FileInputStream(file); } catch (FileNotFoundException e) { //TODO do something intelligent e.printStackTrace(); } try { if(fs!=null) bm=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions); } catch (IOException e) { //TODO do something intelligent e.printStackTrace(); } finally{ if(fs!=null) { try { fs.close(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } //bm=BitmapFactory.decodeFile(path, bfOptions); This one causes error: java.lang.OutOfMemoryError: bitmap size exceeds VM budget im.setImageBitmap(bm); //bm.recycle(); bm=null; } 

    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!

  7. 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.

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

    2. 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.

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

    4. 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.

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

    6. 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.

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

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

    3. 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.

  8. I have seen a lot of questions about OOM exceptions and caching lately. The developer guide has a really good article on this, but some tends to fail on implementing it in a suitable way.

    Because of this I wrote an example application that demonstrates caching in an Android environment. This implementation has not yet gotten an OOM.

    Look at the end of this answer for a link to the source code.

    Requirements:

    • Android API 2.1 or higher (I simply could not manage to get the available memory for an application in API 1.6 – that is the only piece of code that doesn’t work in API 1.6)
    • Android support package

    Screenshot

    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 (75×75) 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.

    MainActivity.java’s critical stuff:

    public void onScrollStateChanged(AbsListView view, int scrollState) { if (view.getId() == android.R.id.list) { // Set scrolling to true only if the user has flinged the  // ListView away, hence we skip downloading a series // of unnecessary bitmaps that the user probably // just want to skip anyways. If we scroll slowly it // will still download bitmaps - that means // that the application won't wait for the user // to lift its finger off the screen in order to // download. if (scrollState == SCROLL_STATE_FLING) { mIsScrolling = true; } else { mIsScrolling = false; mListAdapter.notifyDataSetChanged(); } } } // Inside ListAdapter... @Override public View getView(final int position, View convertView, ViewGroup parent) { View row = convertView; final ViewHolder holder; if (row == null) { LayoutInflater inflater = getLayoutInflater(); row = inflater.inflate(R.layout.main_listview_row, parent, false); holder = new ViewHolder(row); row.setTag(holder); } else { holder = (ViewHolder) row.getTag(); } final Row rowObject = getItem(position); // Look at the loadBitmap() method description... holder.mTextView.setText(rowObject.mText); mCache.loadBitmap(MainActivity.this, rowObject.mBitmapUrl, holder.mImageView, mIsScrolling); return row; } 

    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:

    You can download the source code from https://www.dropbox.com/s/pvr9zyl811tfeem/ListViewImageCache.zip.


    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.

    Report errors in the comments! 🙂

  9. I did the following to take the image and resize it on the fly. Hope this helps

    Bitmap bm; bm = Bitmap.createScaledBitmap(BitmapFactory.decodeFile(filepath), 100, 100, true); mPicture = new ImageView(context); mPicture.setImageBitmap(bm); 
  10. unfortunately if None of the Above works, then Add this to your Manifest file. Inside application tag

     <application android_largeHeap="true" 
  11. 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.

    See here: BitmapFactory OOM driving me nuts

    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;.

    More on that topic here: Is the limit of memory heap only 6M?

    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.

  12. Use this bitmap.recycle(); This helps without any image quality issue.

  13. I have resolved the same issue in the following manner.

    Bitmap b = null; Drawable d; ImageView i = new ImageView(mContext); try { b = Bitmap.createBitmap(320,424,Bitmap.Config.RGB_565); b.eraseColor(0xFFFFFFFF); Rect r = new Rect(0, 0,320 , 424); Canvas c = new Canvas(b); Paint p = new Paint(); p.setColor(0xFFC0C0C0); c.drawRect(r, p); d = mContext.getResources().getDrawable(mImageIds[position]); d.setBounds(r); d.draw(c); /* BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inTempStorage = new byte[128*1024]; b = BitmapFactory.decodeStream(mContext.getResources().openRawResource(mImageIds[position]), null, o2); o2.inSampleSize=16; o2.inPurgeable = true; */ } catch (Exception e) { } i.setImageBitmap(b); 
  14. 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.

    Hope this helps.

  15. There are two issues here….

    • Bitmap memory isn’t in the VM heap but rather in the native heap – see BitmapFactory OOM driving me nuts
    • 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
  16. This worked for me!

    public Bitmap readAssetsBitmap(String filename) throws IOException { try { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; Bitmap bitmap = BitmapFactory.decodeStream(assets.open(filename), null, options); if(bitmap == null) { throw new IOException("File cannot be opened: It's value is null"); } else { return bitmap; } } catch (IOException e) { throw new IOException("File cannot be opened: " + e.getMessage()); } } 
  17. import java.io.File; import java.io.FileInputStream; import android.graphics.Bitmap; import android.graphics.Bitmap.Config; import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.Matrix; import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; public class BitmapHelper { //decodes image and scales it to reduce memory consumption public static Bitmap decodeFile(File bitmapFile, int requiredWidth, int requiredHeight, boolean quickAndDirty) { try { //Decode image size BitmapFactory.Options bitmapSizeOptions = new BitmapFactory.Options(); bitmapSizeOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapSizeOptions); // load image using inSampleSize adapted to required image size BitmapFactory.Options bitmapDecodeOptions = new BitmapFactory.Options(); bitmapDecodeOptions.inTempStorage = new byte[16 * 1024]; bitmapDecodeOptions.inSampleSize = computeInSampleSize(bitmapSizeOptions, requiredWidth, requiredHeight, false); bitmapDecodeOptions.inPurgeable = true; bitmapDecodeOptions.inDither = !quickAndDirty; bitmapDecodeOptions.inPreferredConfig = quickAndDirty ? Bitmap.Config.RGB_565 : Bitmap.Config.ARGB_8888; Bitmap decodedBitmap = BitmapFactory.decodeStream(new FileInputStream(bitmapFile), null, bitmapDecodeOptions); // scale bitmap to mathc required size (and keep aspect ratio) float srcWidth = (float) bitmapDecodeOptions.outWidth; float srcHeight = (float) bitmapDecodeOptions.outHeight; float dstWidth = (float) requiredWidth; float dstHeight = (float) requiredHeight; float srcAspectRatio = srcWidth / srcHeight; float dstAspectRatio = dstWidth / dstHeight; // recycleDecodedBitmap is used to know if we must recycle intermediary 'decodedBitmap' // (DO NOT recycle it right away: wait for end of bitmap manipulation process to avoid // java.lang.RuntimeException: Canvas: trying to use a recycled bitmap [email protected] // I do not excatly understand why, but this way it's OK boolean recycleDecodedBitmap = false; Bitmap scaledBitmap = decodedBitmap; if (srcAspectRatio < dstAspectRatio) { scaledBitmap = getScaledBitmap(decodedBitmap, (int) dstWidth, (int) (srcHeight * (dstWidth / srcWidth))); // will recycle recycleDecodedBitmap recycleDecodedBitmap = true; } else if (srcAspectRatio > dstAspectRatio) { scaledBitmap = getScaledBitmap(decodedBitmap, (int) (srcWidth * (dstHeight / srcHeight)), (int) dstHeight); recycleDecodedBitmap = true; } // crop image to match required image size int scaledBitmapWidth = scaledBitmap.getWidth(); int scaledBitmapHeight = scaledBitmap.getHeight(); Bitmap croppedBitmap = scaledBitmap; if (scaledBitmapWidth > requiredWidth) { int xOffset = (scaledBitmapWidth - requiredWidth) / 2; croppedBitmap = Bitmap.createBitmap(scaledBitmap, xOffset, 0, requiredWidth, requiredHeight); scaledBitmap.recycle(); } else if (scaledBitmapHeight > requiredHeight) { int yOffset = (scaledBitmapHeight - requiredHeight) / 2; croppedBitmap = Bitmap.createBitmap(scaledBitmap, 0, yOffset, requiredWidth, requiredHeight); scaledBitmap.recycle(); } if (recycleDecodedBitmap) { decodedBitmap.recycle(); } decodedBitmap = null; scaledBitmap = null; return croppedBitmap; } catch (Exception ex) { ex.printStackTrace(); } return null; } /** * compute powerOf2 or exact scale to be used as {@link BitmapFactory.Options#inSampleSize} value (for subSampling) * * @param requiredWidth * @param requiredHeight * @param powerOf2 * weither we want a power of 2 sclae or not * @return */ public static int computeInSampleSize(BitmapFactory.Options options, int dstWidth, int dstHeight, boolean powerOf2) { int inSampleSize = 1; // Raw height and width of image final int srcHeight = options.outHeight; final int srcWidth = options.outWidth; if (powerOf2) { //Find the correct scale value. It should be the power of 2. int tmpWidth = srcWidth, tmpHeight = srcHeight; while (true) { if (tmpWidth / 2 < dstWidth || tmpHeight / 2 < dstHeight) break; tmpWidth /= 2; tmpHeight /= 2; inSampleSize *= 2; } } else { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) srcHeight / (float) dstHeight); final int widthRatio = Math.round((float) srcWidth / (float) dstWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; } return inSampleSize; } public static Bitmap drawableToBitmap(Drawable drawable) { if (drawable instanceof BitmapDrawable) { return ((BitmapDrawable) drawable).getBitmap(); } Bitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight(), Config.ARGB_8888); Canvas canvas = new Canvas(bitmap); drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); drawable.draw(canvas); return bitmap; } public static Bitmap getScaledBitmap(Bitmap bitmap, int newWidth, int newHeight) { int width = bitmap.getWidth(); int height = bitmap.getHeight(); float scaleWidth = ((float) newWidth) / width; float scaleHeight = ((float) newHeight) / height; // CREATE A MATRIX FOR THE MANIPULATION Matrix matrix = new Matrix(); // RESIZE THE BIT MAP matrix.postScale(scaleWidth, scaleHeight); // RECREATE THE NEW BITMAP Bitmap resizedBitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height, matrix, false); return resizedBitmap; } }
  18. 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, 1×1 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.

    if (bitmap != null) { bitmap.recycle(); bitmap = null; } if (imageView != null) { imageView.setImageResource(R.drawable.tiny); // This is my 1x1 png. } System.gc(); imageView.setImageBitmap(...); // Do whatever you need to do to load the image you want. 
  19. This works for me.
  20. package com.emil; import java.io.IOException; import java.io.InputStream; import android.graphics.Bitmap; import android.graphics.BitmapFactory; /** * A class to load and process images of various sizes from input streams and file paths. * * @author Emil http://stackoverflow.com/users/220710/emil * */ public class ImageProcessing { public static Bitmap getBitmap(InputStream stream, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig); Bitmap bm = BitmapFactory.decodeStream(stream,null,options); if(ImageProcessing.checkDecode(options)){ return bm; }else{ throw new IOException("Image decoding failed, using stream."); } } public static Bitmap getBitmap(String imgPath, int sampleSize, Bitmap.Config bitmapConfig) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForSampling(sampleSize, bitmapConfig); Bitmap bm = BitmapFactory.decodeFile(imgPath,options); if(ImageProcessing.checkDecode(options)){ return bm; }else{ throw new IOException("Image decoding failed, using file path."); } } public static Dimensions getDimensions(InputStream stream) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions(); BitmapFactory.decodeStream(stream,null,options); if(ImageProcessing.checkDecode(options)){ return new ImageProcessing.Dimensions(options.outWidth,options.outHeight); }else{ throw new IOException("Image decoding failed, using stream."); } } public static Dimensions getDimensions(String imgPath) throws IOException{ BitmapFactory.Options options=ImageProcessing.getOptionsForDimensions(); BitmapFactory.decodeFile(imgPath,options); if(ImageProcessing.checkDecode(options)){ return new ImageProcessing.Dimensions(options.outWidth,options.outHeight); }else{ throw new IOException("Image decoding failed, using file path."); } } private static boolean checkDecode(BitmapFactory.Options options){ // Did decode work? if( options.outWidth<0 || options.outHeight<0 ){ return false; }else{ return true; } } /** * Creates a Bitmap that is of the minimum dimensions necessary * @param bm * @param min * @return */ public static Bitmap createMinimalBitmap(Bitmap bm, ImageProcessing.Minimize min){ int newWidth, newHeight; switch(min.type){ case WIDTH: if(bm.getWidth()>min.minWidth){ newWidth=min.minWidth; newHeight=ImageProcessing.getScaledHeight(newWidth, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } break; case HEIGHT: if(bm.getHeight()>min.minHeight){ newHeight=min.minHeight; newWidth=ImageProcessing.getScaledWidth(newHeight, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } break; case BOTH: // minimize to the maximum dimension case MAX: if(bm.getHeight()>bm.getWidth()){ // Height needs to minimized min.minDim=min.minDim!=null ? min.minDim : min.minHeight; if(bm.getHeight()>min.minDim){ newHeight=min.minDim; newWidth=ImageProcessing.getScaledWidth(newHeight, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } }else{ // Width needs to be minimized min.minDim=min.minDim!=null ? min.minDim : min.minWidth; if(bm.getWidth()>min.minDim){ newWidth=min.minDim; newHeight=ImageProcessing.getScaledHeight(newWidth, bm); }else{ // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } } break; default: // No resize newWidth=bm.getWidth(); newHeight=bm.getHeight(); } return Bitmap.createScaledBitmap(bm, newWidth, newHeight, true); } public static int getScaledWidth(int height, Bitmap bm){ return (int)(((double)bm.getWidth()/bm.getHeight())*height); } public static int getScaledHeight(int width, Bitmap bm){ return (int)(((double)bm.getHeight()/bm.getWidth())*width); } /** * Get the proper sample size to meet minimization restraints * @param dim * @param min * @param multipleOf2 for fastest processing it is recommended that the sample size be a multiple of 2 * @return */ public static int getSampleSize(ImageProcessing.Dimensions dim, ImageProcessing.Minimize min, boolean multipleOf2){ switch(min.type){ case WIDTH: return ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2); case HEIGHT: return ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2); case BOTH: int widthMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.width, min.minWidth, multipleOf2); int heightMaxSampleSize=ImageProcessing.getMaxSampleSize(dim.height, min.minHeight, multipleOf2); // Return the smaller of the two if(widthMaxSampleSize<heightMaxSampleSize){ return widthMaxSampleSize; }else{ return heightMaxSampleSize; } case MAX: // Find the larger dimension and go bases on that if(dim.width>dim.height){ return ImageProcessing.getMaxSampleSize(dim.width, min.minDim, multipleOf2); }else{ return ImageProcessing.getMaxSampleSize(dim.height, min.minDim, multipleOf2); } } return 1; } public static int getMaxSampleSize(int dim, int min, boolean multipleOf2){ int add=multipleOf2 ? 2 : 1; int size=0; while(min<(dim/(size+add))){ size+=add; } size = size==0 ? 1 : size; return size; } public static class Dimensions { int width; int height; public Dimensions(int width, int height) { super(); this.width = width; this.height = height; } @Override public String toString() { return width+" x "+height; } } public static class Minimize { public enum Type { WIDTH,HEIGHT,BOTH,MAX } Integer minWidth; Integer minHeight; Integer minDim; Type type; public Minimize(int min, Type type) { super(); this.type = type; switch(type){ case WIDTH: this.minWidth=min; break; case HEIGHT: this.minHeight=min; break; case BOTH: this.minWidth=min; this.minHeight=min; break; case MAX: this.minDim=min; break; } } public Minimize(int minWidth, int minHeight) { super(); this.type=Type.BOTH; this.minWidth = minWidth; this.minHeight = minHeight; } } /** * Estimates size of Bitmap in bytes depending on dimensions and Bitmap.Config * @param width * @param height * @param config * @return */ public static long estimateBitmapBytes(int width, int height, Bitmap.Config config){ long pixels=width*height; switch(config){ case ALPHA_8: // 1 byte per pixel return pixels; case ARGB_4444: // 2 bytes per pixel, but depreciated return pixels*2; case ARGB_8888: // 4 bytes per pixel return pixels*4; case RGB_565: // 2 bytes per pixel return pixels*2; default: return pixels; } } private static BitmapFactory.Options getOptionsForDimensions(){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds=true; return options; } private static BitmapFactory.Options getOptionsForSampling(int sampleSize, Bitmap.Config bitmapConfig){ BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = sampleSize; options.inScaled = false; options.inPreferredConfig = bitmapConfig; return options; } }
  21. 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 🙂

    private Bitmap decodeFile(String fPath) { // Decode image size BitmapFactory.Options opts = new BitmapFactory.Options(); /* * If set to true, the decoder will return null (no bitmap), but the * out... fields will still be set, allowing the caller to query the * bitmap without having to allocate the memory for its pixels. */ opts.inJustDecodeBounds = true; opts.inDither = false; // Disable Dithering mode opts.inPurgeable = true; // Tell to gc that whether it needs free // memory, the Bitmap can be cleared opts.inInputShareable = true; // Which kind of reference will be used to // recover the Bitmap data after being // clear, when it will be used in the // future BitmapFactory.decodeFile(fPath, opts); // The new size we want to scale to final int REQUIRED_SIZE = 70; // Find the correct scale value.  int scale = 1; if (opts.outHeight > REQUIRED_SIZE || opts.outWidth > REQUIRED_SIZE) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) opts.outHeight / (float) REQUIRED_SIZE); final int widthRatio = Math.round((float) opts.outWidth / (float) REQUIRED_SIZE); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. scale = heightRatio < widthRatio ? heightRatio : widthRatio;// } // Decode bitmap with inSampleSize set opts.inJustDecodeBounds = false; opts.inSampleSize = scale; Bitmap bm = BitmapFactory.decodeFile(fPath, opts).copy( Bitmap.Config.RGB_565, false); return bm; } 

    I hope this will help the buddies facing the same problem!

    for more please refer this

  22. 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.

    @Override public View getView(final int position, View convertView, final ViewGroup parent) { if(convertView == null){ LayoutInflater inflater = (LayoutInflater) mContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE); convertView = inflater.inflate(R.layout.spinner_row, null); } ... 
  23. This issue only happens in Android emulators. I also faced this issue in an emulator but when I checked in a device then it worked fine.

    So please check in a device. It may be run in device.

  24. 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.

    @Override public void onDestroy() { Cleanup(); super.onDestroy(); } private void Cleanup() { bitmap.recycle(); System.gc(); Runtime.getRuntime().gc(); } 
  25. All the solutions here require setting a IMAGE_MAX_SIZE. This limits devices with more powerful hardware and if the image size is too low it looks ugly on the HD screen.

    I came out with a solution that works with my Samsung Galaxy S3 and several other devices including less powerful ones, with better image quality when a more powerful device is used.

    The gist of it is to calculate the maximum memory allocated for the app on a particular device, then set the scale to be lowest possible without exceeding this memory. Here’s the code:

    public static Bitmap decodeFile(File f) { Bitmap b = null; try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options(); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(f); try { BitmapFactory.decodeStream(fis, null, o); } finally { fis.close(); } // In Samsung Galaxy S3, typically max memory is 64mb // Camera max resolution is 3264 x 2448, times 4 to get Bitmap memory of 30.5mb for one bitmap // If we use scale of 2, resolution will be halved, 1632 x 1224 and x 4 to get Bitmap memory of 7.62mb // We try use 25% memory which equals to 16mb maximum for one bitmap long maxMemory = Runtime.getRuntime().maxMemory(); int maxMemoryForImage = (int) (maxMemory / 100 * 25); // Refer to // http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html // A full screen GridView filled with images on a device with // 800x480 resolution would use around 1.5MB (800*480*4 bytes) // When bitmap option's inSampleSize doubled, pixel height and // weight both reduce in half int scale = 1; while ((o.outWidth / scale) * (o.outHeight / scale) * 4 > maxMemoryForImage) scale *= 2; // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options(); o2.inSampleSize = scale; fis = new FileInputStream(f); try { b = BitmapFactory.decodeStream(fis, null, o2); } finally { fis.close(); } } catch (IOException e) { } return b; } 

    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.

    Hope this helps someone out there..

  26. use these code for every image in select from SdCard or drewable to convert bitmap object.

    Resources res = getResources(); WindowManager window = (WindowManager) getSystemService(Context.WINDOW_SERVICE); Display display = window.getDefaultDisplay(); @SuppressWarnings("deprecation") int width = display.getWidth(); @SuppressWarnings("deprecation") int height = display.getHeight(); try { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } bitmap = Bitmap.createScaledBitmap(BitmapFactory .decodeFile(ImageData_Path.get(img_pos).getPath()), width, height, true); } catch (OutOfMemoryError e) { if (bitmap != null) { bitmap.recycle(); bitmap = null; System.gc(); } BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Config.RGB_565; options.inSampleSize = 1; options.inPurgeable = true; bitmapBitmap.createScaledBitmap(BitmapFactory.decodeFile(ImageData_Path.get(img_pos) .getPath().toString(), options), width, height,true); } return bitmap; 

    use your image path instend of ImageData_Path.get(img_pos).getPath() .

  27. 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.

  28. My 2 cents: i solved my OOM errors with bitmaps by:

    a) scaling my images by a factor of 2

    b) using Picasso library in my custom Adapter for a ListView, with a one-call in getView like this: Picasso.with(context).load(R.id.myImage).into(R.id.myImageView);

  29. Such OutofMemoryException cannot be totally resolved by calling the System.gc() and so on .

    By referring to the Activity Life Cycle

    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.)

  30. This code will help to load large bitmap from drawable

    public class BitmapUtilsTask extends AsyncTask<Object, Void, Bitmap> { Context context; public BitmapUtilsTask(Context context) { this.context = context; } /** * Loads a bitmap from the specified url. * * @param url The location of the bitmap asset * @return The bitmap, or null if it could not be loaded * @throws IOException * @throws MalformedURLException */ public Bitmap getBitmap() throws MalformedURLException, IOException { // Get the source image's dimensions int desiredWidth = 1000; BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); int srcWidth = options.outWidth; int srcHeight = options.outHeight; // Only scale if the source is big enough. This code is just trying // to fit a image into a certain width. if (desiredWidth > srcWidth) desiredWidth = srcWidth; // Calculate the correct inSampleSize/scale value. This helps reduce // memory use. It should be a power of 2 int inSampleSize = 1; while (srcWidth / 2 > desiredWidth) { srcWidth /= 2; srcHeight /= 2; inSampleSize *= 2; } // Decode with inSampleSize options.inJustDecodeBounds = false; options.inDither = false; options.inSampleSize = inSampleSize; options.inScaled = false; options.inPreferredConfig = Bitmap.Config.ARGB_8888; options.inPurgeable = true; Bitmap sampledSrcBitmap; sampledSrcBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.green_background , options); return sampledSrcBitmap; } /** * The system calls this to perform work in a worker thread and delivers * it the parameters given to AsyncTask.execute() */ @Override protected Bitmap doInBackground(Object... item) { try { return getBitmap(); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return null; } } 
  31. It seems the images you have used is very large in size.so some older devices crashes occurs due to heap memory full.In older devices(honey comb or ICS or any low end model devices) try to use android:largeHeap="true" in the manifest file under application tag or reduce the size of the bitmap by using below code.

    Bitmap bMap; BitmapFactory.Options options = new BitmapFactory.Options(); options.InSampleSize = 8; bMap= BitmapFactory.DecodeFile(imgFile.AbsolutePath, options); 

    you can also give 4 or 12 or 16 to reduce the bitmap size

  32. BitmapFactory.Options options = new Options(); options.inSampleSize = 32; //img = BitmapFactory.decodeFile(imageids[position], options); Bitmap theImage = BitmapFactory.decodeStream(imageStream,null, options); Bitmap img=theImage.copy(Bitmap.Config.RGB_565,true); theImage.recycle(); theImage = null; System.gc(); //ivlogdp.setImageBitmap(img); Runtime.getRuntime().gc(); 
  33. I tried Thomas Vervest’s approach, but it returns a scale of 1 for image size 2592×1944 when IMAGE_MAX_SIZE is 2048.

    This version worked for me based on all the other comments provided by others:

    private Bitmap decodeFile (File f) { Bitmap b = null; try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options (); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream (f); try { BitmapFactory.decodeStream (fis, null, o); } finally { fis.close (); } int scale = 1; for (int size = Math.max (o.outHeight, o.outWidth); (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale); // Decode with inSampleSize BitmapFactory.Options o2 = new BitmapFactory.Options (); o2.inSampleSize = scale; fis = new FileInputStream (f); try { b = BitmapFactory.decodeStream (fis, null, o2); } finally { fis.close (); } } catch (IOException e) { } return b; } 
  34. use this concept this will help you, After that set the imagebitmap on image view

    public static Bitmap convertBitmap(String path) { Bitmap bitmap=null; BitmapFactory.Options bfOptions=new BitmapFactory.Options(); bfOptions.inDither=false; //Disable Dithering mode bfOptions.inPurgeable=true; //Tell to gc that whether it needs free memory, the Bitmap can be cleared bfOptions.inInputShareable=true; //Which kind of reference will be used to recover the Bitmap data after being clear, when it will be used in the future bfOptions.inTempStorage=new byte[32 * 1024]; File file=new File(path); FileInputStream fs=null; try { fs = new FileInputStream(file); } catch (FileNotFoundException e) { e.printStackTrace(); } try { if(fs!=null) { bitmap=BitmapFactory.decodeFileDescriptor(fs.getFD(), null, bfOptions); } } catch (IOException e) { e.printStackTrace(); } finally{ if(fs!=null) { try { fs.close(); } catch (IOException e) { e.printStackTrace(); } } } return bitmap; } 

    If you want to make a small image from large image with height and width like 60 and 60 and scroll the listview fast then use this concept

    public static Bitmap decodeSampledBitmapFromPath(String path, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFile(path, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; Bitmap bmp = BitmapFactory.decodeFile(path, options); return bmp; } public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { if (width > height) { inSampleSize = Math.round((float) height / (float) reqHeight); } else { inSampleSize = Math.round((float) width / (float) reqWidth); } } return inSampleSize; } 

    I hope it will help you much.

    You can take help from developer site Here

  35. private Bitmap decodeBitmapFile (File f) { Bitmap bitmap = null; try { // Decode image size BitmapFactory.Options o = new BitmapFactory.Options (); o.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream (f); try { BitmapFactory.decodeStream (fis, null, o); } finally { fis.close (); } int scale = 1; for (int size = Math.max (o.outHeight, o.outWidth); (size>>(scale-1)) > IMAGE_MAX_SIZE; ++scale); // Decode with input-stram SampleSize BitmapFactory.Options o2 = new BitmapFactory.Options (); o2.inSampleSize = scale; fis = new FileInputStream (f); try { bitmap = BitmapFactory.decodeStream (fis, null, o2); } finally { fis.close (); } } catch (IOException e) { } return bitmap ; }
  36. To fix OutOfMemory you should do something like that please try this code

    public Bitmap loadBitmap(String URL, BitmapFactory.Options options) { Bitmap bitmap = null; InputStream in = null; options.inSampleSize=4; try { in = OpenHttpConnection(URL); Log.e("In====>", in+""); bitmap = BitmapFactory.decodeStream(in, null, options); Log.e("URL====>", bitmap+""); in.close(); } catch (IOException e1) { } return bitmap; } 

    and

    try { BitmapFactory.Options bmOptions; bmOptions = new BitmapFactory.Options(); bmOptions.inSampleSize = 1; if(studentImage != null){ galleryThumbnail= loadBitmap(IMAGE_URL+studentImage, bmOptions); } galleryThumbnail=getResizedBitmap(galleryThumbnail, imgEditStudentPhoto.getHeight(), imgEditStudentPhoto.getWidth()); Log.e("Image_Url==>",IMAGE_URL+studentImage+""); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } 
  37. After looking through all the answers, I was surprised to see that no one mentioned the Glide API for handling images. Great library, and abstracts out all the complexity of bitmap management. You can load and resize images quickly with this library and a single line of code.

     Glide.with(this).load(yourImageResource).into(imageview); 

    You can get the repository here: https://github.com/bumptech/glide

  38. I used Decode File Descriptor which worked for me :

     FileInputStream fileInputStream = null; try { fileInputStream = new FileInputStream(file); FileDescriptor fd = fileInputStream.getFD(); Bitmap imageBitmap = decodeSampledBitmapFromDescriptor(fd , 612, 816); imageView.setImageBitmap(imageBitmap); } catch (Exception e) { e.printStackTrace(); }finally { if(fileInputStream != null){ try { fileInputStream.close(); } catch (IOException e) { e.printStackTrace(); } } } 

    Code to decode Sampled Bitmap From File Descriptor:

     /** * Decode and sample down a bitmap from a file input stream to the requested width and height. * * @param fileDescriptor The file descriptor to read from * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return A bitmap sampled down from the original with the same aspect ratio and dimensions * that are equal to or greater than the requested width and height */ public static Bitmap decodeSampledBitmapFromDescriptor( FileDescriptor fileDescriptor, int reqWidth, int reqHeight) { // First decode with inJustDecodeBounds=true to check dimensions final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); // Calculate inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); // Decode bitmap with inSampleSize set options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fileDescriptor, null, options); } /** * Calculate an inSampleSize for use in a {@link android.graphics.BitmapFactory.Options} object when decoding * bitmaps using the decode* methods from {@link android.graphics.BitmapFactory}. This implementation calculates * the closest inSampleSize that will result in the final decoded bitmap having a width and * height equal to or larger than the requested width and height. This implementation does not * ensure a power of 2 is returned for inSampleSize which can be faster when decoding but * results in a larger bitmap which isn't as useful for caching purposes. * * @param options An options object with out* params already populated (run through a decode* * method with inJustDecodeBounds==true * @param reqWidth The requested width of the resulting bitmap * @param reqHeight The requested height of the resulting bitmap * @return The value to be used for inSampleSize */ public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { // Calculate ratios of height and width to requested height and width final int heightRatio = Math.round((float) height / (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee a final image // with both dimensions larger than or equal to the requested height and width. inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio; // This offers some additional logic in case the image has a strange // aspect ratio. For example, a panorama may have a much larger // width than height. In these cases the total pixels might still // end up being too large to fit comfortably in memory, so we should // be more aggressive with sample down the image (=larger inSampleSize). final float totalPixels = width * height; // Anything more than 2x the requested pixels we'll sample down further final float totalReqPixelsCap = reqWidth * reqHeight * 2; while (totalPixels / (inSampleSize * inSampleSize) > totalReqPixelsCap) { inSampleSize++; } } return inSampleSize; } 
  39. This will get an appropriate bitmap and reduce memory consumption

    JAVA

    Bitmap bm = null; BitmapFactory.Options bmpOption = new BitmapFactory.Options(); bmpOption.inJustDecodeBounds = true; FileInputStream fis = new FileInputStream(file); BitmapFactory.decodeStream(fis, null, bmpOption); fis.close(); int scale = 1; if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) { scale = (int)Math.pow(2, (int) Math.ceil(Math.log(IMAGE_MAX_SIZE / (double) Math.max(bmpOption.outHeight, bmpOption.outWidth)) / Math.log(0.5))); } BitmapFactory.Options bmpOption2 = new BitmapFactory.Options(); bmpOption2.inSampleSize = scale; fis = new FileInputStream(file); bm = BitmapFactory.decodeStream(fis, null, bmpOption2); fis.close(); 

    Kotlin

    val bm:Bitmap = null val bmpOption = BitmapFactory.Options() bmpOption.inJustDecodeBounds = true val fis = FileInputStream(file) BitmapFactory.decodeStream(fis, null, bmpOption) fis.close() val scale = 1 if (bmpOption.outHeight > IMAGE_MAX_SIZE || bmpOption.outWidth > IMAGE_MAX_SIZE) { scale = Math.pow(2.0, Math.ceil((Math.log((IMAGE_MAX_SIZE / Math.max(bmpOption.outHeight, bmpOption.outWidth) as Double)) / Math.log(0.5))).toInt().toDouble()).toInt() } val bmpOption2 = BitmapFactory.Options() bmpOption2.inSampleSize = scale fis = FileInputStream(file) bm = BitmapFactory.decodeStream(fis, null, bmpOption2) fis.close() 
  40. If you are lazy like me, you can start using Picasso library to load images.

    Picasso.with(context).load(R.drawable.landing_screen).into(imageView1); Picasso.with(context).load("file:///android_asset/DvpvklR.png").into(imageView2); Picasso.with(context).load(new File(...)).into(imageView3); 
  41. Best practices to avoid memory leaks or OOM for bitmap

    1. Do not keep bitmap references for long-lived to a Context / Activity.
    2. If you are using a large bitmap as background or something in your application then don’t pull the full image into the main memory. You can use the insample size property of bitmap to bring the size your screen needs.
    3. Clean bitmap reference once no longer use.
  42. I needed to load a large size image into Bitmap and I used Glide to solve this issue. First check the image size with BitmapFactory.Options using inJustDecodeBounds set to true, then use Glide to get Bitmap object. I used the profiler to check memory usage but I did not see any memory spike like I did when I was using BitmapFactory.decodeFile(). I am writing in c# as I use Xamarin, so need a little tweak to use in Java. Glide library documentation

    private Bitmap DecodeFile(File file) { // Decode image size BitmapFactory.Options options = new BitmapFactory.Options(); // setting inJustDecodeBounds to true won't load the file into memory,  // but gives you the actual file size. options.InJustDecodeBounds = true; BitmapFactory.decodeStream(new FileInputStream(file), null, options); int actualWidth = options.OutWidth; int actualHeight = options.OutHeight; var ratio = (double)actualHeight / actualWidth; // Default to 800 x 600. changed the size whatever you need. var desiredWidth = 800; var desiredHeight = 600; if(actualHeight > actualWidth) { var ratio = (double)actualWidth / actualHeight; var futureTarget = Glide.With(Application.Context) .AsBitmap() .Load(file) .SetDiskCacheStrategy(DiskCacheStrategy.None) .SkipMemoryCache(true) .Submit((int)(desiredWidth * ratio), desiredWidth); bitmap = futureTarget.Get() as Bitmap; } else { var ratio = (double)actualHeight / actualWidth; var futureTarget = Glide.With(Application.Context) .AsBitmap() .Load(file) .SetDiskCacheStrategy(DiskCacheStrategy.None) .SkipMemoryCache(true) .Submit(desiredWidth, (int)(desiredWidth * ratio)); bitmap = futureTarget.Get() as Bitmap; }return bitmap;} 
  43. Add the following lines to your manifest.xml file:

    <application android:hardwareAccelerated="false" android:largeHeap="true"> <activity> </activity> </application> 
  44. After setting an bitmap to imageview, recycle it like this:

    bitmap.recycle(); bitmap=null; 

Tasg: android, image

Answer Link
jidam
  • Unable to run NoraUI mvn verify goal
  • Unable to run my app on emulator in VS Code
  • Unable to run multiple instances of libVLC(MobileVLCKit) in IOS via flutter framework
  • Unable to run make on griddb source on ubuntu 20.04 (building from source)
  • Unable to run latexindent macOS Monterey 12.0.1
  • Unable to run kotlinc-native command
  • Unable to run JUnit Test… Java.lang.ExceptionInInitializerError (Android Studio)
  • Unable to run java with -Xmx > 966m
  • Unable to run ionic cap run android from wsl2 inorder to start android emulator
  • Unable to run Intel HAXM installer: Cannot start process, the working directory does not exist
  • fs
  • Unable to run Google Analytics sample code
  • unable to run flutter run after upgarding to flutter 2.8.0 from 2.5.3
  • Unable to run Django with PostgreSQL in Docker
  • Unable to Run Container Using testcontainers
  • Unable to run ClojureScript Hello World program, Error building classpath. Error reading edn.
  • unable to run client command for apache karaf 4.3.3 through remote server
  • Unable to run c program 2nd time using eclipse
  • unable to run c++ in visual studio code on m1 chipset
  • Unable to run Android Instrumented Tests
  • Unable to run adb, check your Android SDK installation and ANDROID_SDK_ROOT environment variable: …AndroidSdkplatform-toolsadb.exe
  • Unable to run a singlespecific .spec.ts file through angular cli using ng test –include option
  • Unable to run a Mango query
  • Unable to return response back to view in laravel from package
  • Unable to return object reference in std::optional
  • Unable to return NULL in a function that expects an integer return type
  • Unable to return correct change in JavaScript Cash Register
  • Unable to retrieve version information from Elasticsearch nodes. Request timed out
  • Unable to retrieve values from Axios Response data
  • Unable to retrieve dotenv JWT secret Error: secretOrPrivateKey must have a value
  • Unable to resolve your shell environment
  • Unable to resolve token for FCM while implementing Push notification for Xamarin
  • Unable to resolve the request yii
  • Unable to resolve service for type Swashbuckle.AspNetCore.Swagger.ISwaggerProvider
  • Unable to resolve service for type Microsoft.EntityFrameworkCore.Diagnostics.IDiagnosticsLogger