0

I've been strugling with this problem for a while now. Still, I have some hope left.

One of my activities downloads an image by using an AsyncTask. It saves the image in a Bitmap and then displays it in an Image View. This is the code:

@Override
    protected String doInBackground(String... urls) {
        try {

            URL url = new URL(urls[0]);

            // First decode with inJustDecodeBounds=true to check dimensions
            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeStream(url.openConnection()
                    .getInputStream(), null, options);

            // Calculate inSampleSize
            options.inSampleSize = calculateInSampleSize(options, 270, 173);

            // Decode bitmap with inSampleSize set
            options.inJustDecodeBounds = false;
            options.inPurgeable = true;
            options.inInputShareable = true;
            bmp = BitmapFactory.decodeStream(url.openConnection()
                    .getInputStream(), null, options);

        } catch (Exception e) {
            Log.e(MainActivity.class.toString(),
                    "No se pudo descargar la imagen");
        }
        return "";
    }

    @Override
    protected void onPostExecute(String result) {
        if (bmp != null) {

            imagenNoticia.setImageBitmap(bmp);
            imagenNoticia.setVisibility(View.VISIBLE);
        }

    }
}

But after navigating through the app for a while, by opening and closing this activity, I get an OutOfMemoryError right at the line of code BitmapFactory.decodeStream. I'm sure this activity never duplicates, so I just one of its type at a time.

As you guys can see, I've used several of the best practices about eficiently decoding bitmaps. Like downsampling the received images (inJustDecodeBounds).

I would like to know what else I can do to avoid this error. Does anybody have an idea about it?

Next comes the error.

Thanks! Any help would be appreciate it.

12-16 18:19:17.686: E/AndroidRuntime(13321): FATAL EXCEPTION: AsyncTask #1
12-16 18:19:17.686: E/AndroidRuntime(13321): java.lang.RuntimeException: An error         occured while executing doInBackground()
12-16 18:19:17.686: E/AndroidRuntime(13321):    at     android.os.AsyncTask$3.done(AsyncTask.java:299)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at     java.util.concurrent.FutureTask$Sync.innerSetException(FutureTask.java:273)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at     java.util.concurrent.FutureTask.setException(FutureTask.java:124)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:307)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.util.concurrent.FutureTask.run(FutureTask.java:137)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:230)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1076)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:569)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.lang.Thread.run(Thread.java:856)
12-16 18:19:17.686: E/AndroidRuntime(13321): Caused by: java.lang.OutOfMemoryError
12-16 18:19:17.686: E/AndroidRuntime(13321):    at android.graphics.BitmapFactory.nativeDecodeStream(Native Method)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:652)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at     com.mobilemedianet.larepublica.activity.NotiDetalle$CargarNoticias.doInBackground(NotiDetalle.java:496)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at com.mobilemedianet.larepublica.activity.NotiDetalle$CargarNoticias.doInBackground(NotiDetalle.java:1)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at android.os.AsyncTask$2.call(AsyncTask.java:287)
12-16 18:19:17.686: E/AndroidRuntime(13321):    at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:305)
12-16 18:19:17.686: E/AndroidRuntime(13321):    ... 5 more
jmoukel
  • 794
  • 6
  • 18
  • Possible reasons: (1) `calculateInSampleSize` method returns the wrong value so the image is never sampled, (2) Memory leak in the app caused by unwanted references to old bitmap. Please post your complete code for us to debug.. – Amulya Khare Dec 17 '13 at 02:56
  • Please provide the following details. 1. device used for testing 2. OS Version. 3. Post the calculateInSampleSize Method 4. Did you check the dimensions of the bitmap before decoding ? Refer to this link http://stackoverflow.com/questions/20617094/outofmemoryerror-with-image-selection-in-android-how-to-resize-image-before-dec/20617305#20617305 – Prem Dec 17 '13 at 03:05

2 Answers2

0

You have references to two bitmaps: one in the view and the other in the AsyncTask. What you can do, at least in theory:

1) Save the downloaded bitmap to disk (sd-card etc.). In this case you will be able to first stop using the first image and only then use the second one.

2) Move downloading the image to a separate process. It will have its own heap quote. Scaling probably should go to the same (second) process. In the XML, activities and services have the process attribute. In general, killing a process is a good trick to kill Java garbage.

3) There are low-memory callbacks in the life cycle of processes and activities. Probably this is the moment when you can release the image in the view (I myself did not try it and am not even sure whether these callbacks are actually called).

4) Probably you can just place different activities of your application in different processes (this depends on what your application is).

18446744073709551615
  • 16,368
  • 4
  • 94
  • 127
  • I think we need to know who holds on to `bmp` reference before we do any drastic implementation changes. – Magnus Dec 23 '13 at 19:09
0

I think you're having a variable scope problem, who owns the bmp variable? Is it cleared from your code? The AsyncTask is it nulled out after the imageview has set the bitmap? Otherwise you're leaking that bmp (it's not freed!).

Rewrite AsyncTask option

I think you're AsyncTask should extend AsyncTask<String, Void, Bitmap> giving you (and change lines to what I have written here):

@Override
protected String doInBackground(String... urls) {
    try {
        // omitted some lines

        return BitmapFactory.decodeStream(url.openConnection()
                .getInputStream(), null, options);

    } catch (Exception e) {
        Log.e(MainActivity.class.toString(),
                "No se pudo descargar la imagen");
    }
    return null;
}

@Override
protected void onPostExecute(Bitmap result) {
    if (result != null) {
        imagenNoticia.setImageBitmap(result);
        imagenNoticia.setVisibility(View.VISIBLE);
    }
}

The above AsyncTask does not hold on to any bitmap reference after onPostExecute has been executed.

Other option

If you do not want to do the above you should consider nulling out the bmp variable last in onPostExecute and finally implement the onCancelled callback in AsyncTask, if and only if you call cancel on your task, and clear the bmp ref in onCancelled callback then (since onPostExecutewon't run in that case)

Magnus
  • 1,483
  • 11
  • 14