0

I have an application that gets a fixed amount of images from the camera preview and converts them into a list of Bitmaps. For that purpose I have the following code:

private Camera.PreviewCallback SetPreviewCallBack() {
    return new Camera.PreviewCallback() {
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
    List<Bitmap> imageList = new ArrayList<Bitmap>();
    for (int i=0; i<30; i++) // Let's suppose this is the real loop. 
                             // It's not, as the real loop takes each camera preview frame, 
                             // instead of inserting the same one 30 times. 
                             // But for this example, it's OK
    {
        imageList.add(GetBitmap(
                    data, 
                    previewWidth, // Calculated
                    previewHeight, // Calculated 
                    previewFormat, // Calculated
                    previewRotation)); // Calculated
    }

}

private Bitmap GetBitmap(byte[] data, int width, int height, int previewFormat, int rotation) { 

    YuvImage yuv = new YuvImage(data, previewFormat, width, height, null);

    ByteArrayOutputStream out = new ByteArrayOutputStream();
    yuv.compressToJpeg(new Rect(0, 0, width, height), 50, out);

    byte[] bytes = out.toByteArray();
    final Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);

    Bitmap imageResult = RotateImage(bitmap, 4 - rotation);
    bitmap.recycle();

    return imageResult;
} 

private Bitmap RotateImage(Bitmap rotateImage, int rotation) {

    Matrix matrix = new Matrix();
    switch (rotation) {
    case 1:
        matrix.postRotate(270);
        break;
    case 2:
        matrix.postRotate(180);
        break;
    case 3:
        matrix.postRotate(90);
        break;
    }

    return Bitmap.createBitmap(rotateImage, 0, 0, rotateImage.getWidth(),
            rotateImage.getHeight(), matrix, true);
}

What I do is: - I store this image on a Singleton class in order to access it from another Activity contanied in my same application. - I call again this piece of code (when some event happens) and repeat the image extraction/saving process.

03-05 09:35:13.339: E/AndroidRuntime(8762): FATAL EXCEPTION: Thread-818
03-05 09:35:13.339: E/AndroidRuntime(8762): java.lang.OutOfMemoryError
03-05 09:35:13.339: E/AndroidRuntime(8762):     at android.graphics.Bitmap.nativeCreate(Native Method)
03-05 09:35:13.339: E/AndroidRuntime(8762):     at android.graphics.Bitmap.createBitmap(Bitmap.java:726)
03-05 09:35:13.339: E/AndroidRuntime(8762):     at android.graphics.Bitmap.createBitmap(Bitmap.java:703)
03-05 09:35:13.339: E/AndroidRuntime(8762):     at android.graphics.Bitmap.createBitmap(Bitmap.java:636)
03-05 09:35:13.339: E/AndroidRuntime(8762):     at com.facephi.sdk.ui.CameraPreview.RotateImage(CameraPreview.java:779)
03-05 09:35:13.339: E/AndroidRuntime(8762):     at com.facephi.sdk.ui.CameraPreview.GetBitmap(CameraPreview.java:712)

I'm trying to use best practices to remove in the correct way unused Bitmaps as posted here, but with no luck...

Any idea on why I'm having this OOM Error?

Sonhja
  • 8,230
  • 20
  • 73
  • 131
  • Set `android:largeHeap = "true"` for activity and also use `BitmapFactory.options` to scale your bitmap objects with `inSampleSize` property. – Piyush Mar 05 '15 at 08:53
  • @Devill using `android:largeHeap` solves my problem, but I'm afraid it will finally crash as it only increases the amount of memory the application will have, but at some point I will reach it too. And what will `BitmapFactory.options` do? – Sonhja Mar 05 '15 at 08:57
  • OOM means it exceeds the memory limit of heap size. For `BitmapFactory.Options` you can check http://developer.android.com/reference/android/graphics/BitmapFactory.Options.html – Piyush Mar 05 '15 at 08:59

2 Answers2

2

You have very limited memory in Android which is why you're getting this exception. In general, you shouldn't need to store so many images in memory and should only load them when you need them (ie showing them or doing something with them) and should dispose of them (recycle()) as soon as you're done with them. Additionally, you should only load them with as low a resolution as you can get away with.

Having said all these, and not knowing why you would have to have them all in memory (you may have a legitimate reason), you can increase the heap size for your app by specifying the appropriate attribute in your manifest (http://developer.android.com/guide/topics/manifest/application-element.html#largeHeap) but even that doesn't guarantee you can load too many images.

Bear in mind, by default, Android will use 4 bytes per pixel for your images so if you have 30 images, each with 1 million pixel density, you're using 120 million bytes or roughly 120MB of memory. In contrast, the default allocated byte to your app can be as low as a measly 16mb (depends on many cases. See here for more details: Android heap size on different phones/devices and OS versions).

If you can somehow change your code so that you don't have to have them all loaded in memory, you can have a look at bitmap caching (http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html) which can greatly help with your memory management problems.

Community
  • 1
  • 1
kha
  • 19,123
  • 9
  • 34
  • 67
  • 1
    It should be noted that you can reduce the amount of bytes that are allocated per pixel by allocating the bitmaps with Config.RGB_565 (these bitmaps won't have an alpha channel slightly lower quality color, but if you don't need that, then it will reduce your memory allocation by half). – Gil Moshayof Mar 05 '15 at 08:59
  • @kha Interesting... let me try. Of course, as you mentioned, I have a legitimate reason on why I need these images. I'm insisting on not storing them, but till now I have to get them. I'm gonna check your links. They look interesting. – Sonhja Mar 05 '15 at 09:00
  • @GilMoshayof Very fair point. I was just referring to the default behavior. I'm changing it myself to RGB_565 but most people will just use the default which is why I wrote that as an example. The example in the codebase in the original question wasn't changing it. – kha Mar 05 '15 at 09:10
  • @Sonhja May I suggest an alternative to your solution? You're storing them in a singleton in order to pass them between activities. Can you instead save the bitmaps to a temporary storage, pass the temporary file names and then removing them when you're done with them in the second activity? There's a risk associated here with not deleting these files but I'm sure you can figure out a way to clean up in case of an exception :). – kha Mar 05 '15 at 09:14
  • Do you mean passing the image list as a List in which I pass the path of each image? Or maybe database? I used to store them temporarly in .nomedia path, and pass only the String path. But I was figuring out how to manage with image list directly. – Sonhja Mar 05 '15 at 09:21
  • @Sonhja Yep. that's exactly what I meant. Loading bitmaps is not that slow and if you combine it with something like a disk cache, it can be even more efficient. If you're only doing it for experimental reasons (and I commend you for it since this way you'll learn a lot of pitfalls with handling bitmaps in Android), Try using a cache to see how they work. You can also use this fantastic library (https://github.com/nostra13/Android-Universal-Image-Loader) which does most of the work for you. I wholeheartedly recommend this library when working with images in Android. It's a huge time saver. – kha Mar 05 '15 at 09:27
  • Wow thanks. I'm definitely going to try this. I tried with a list of strings, but I want to experiment with cache as you mention. I'll let you know. – Sonhja Mar 05 '15 at 09:30
1

1. Try using

BitmapFactory.Options options=new BitmapFactory.Options();
options.inSampleSize = 8;
options.inDither = false;
options.inPurgeable = true;
options.inInputShareable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.length, options);

This will greatly help save memory.

2. Do not "store" your Bitmap object in a singleton or anywhere else. This will always lead to OutOfMemoryError. Create the Bitmap anew every time you want to use it.

EDIT:

See also the official ThreadSample example and the Displaying Bitmaps Efficiently tutorial.

Yash Sampat
  • 30,051
  • 12
  • 94
  • 120
  • What is the `is` parameter? – Sonhja Mar 05 '15 at 09:01
  • I'm storing this image list on a `Singleton` because I need to pass this image list between two activities. If I try to parse it, I always exceed the `Parcelable` limit. But let me try your solution. If I'm lucky enought, using this `BitmapFactory.Options` will reduce image size enought so I can directly parse it and I don't have to store it in a `Singleton` class. – Sonhja Mar 05 '15 at 09:04
  • I have edited the answer as you are using `decodeByteArray()` instead of `decodeStream()` – Yash Sampat Mar 05 '15 at 09:05
  • @Sonhja: where have you reached so far ? – Yash Sampat Mar 05 '15 at 09:25
  • Sorry, done with this. Now I don't use the `Singleton` class anymore and compress the images as you suggested (sorry, finished right now). And I'm parceling the image list to my main activity. Now it seems to work, so thank you very much! I'm gonna strugle a little bit on which @kha is saying about using cache memory. – Sonhja Mar 05 '15 at 10:16
  • Actually for me this is not the best solution, as the image loose so much quality and they are not good enough. So I'm gonna struggle with another solution. – Sonhja Mar 05 '15 at 10:30
  • [Displaying Bitmaps Efficiently](http://developer.android.com/training/displaying-bitmaps/index.html) – Yash Sampat Mar 05 '15 at 10:31
  • see also the official [ThreadSample](https://developer.android.com/training/load-data-background/setup-loader.html) tutorial where they show how to load very large images in Android. – Yash Sampat Mar 05 '15 at 10:42