BitmapFactory.decodeStream out of memory despite u

2019-01-08 09:42发布

问题:

I have read many related posts concerning memory allocation problems with decoding bitmaps, but am still unable to find the solution to the following problem even after using the code provided in the official website.

Here is my code:

public static Bitmap decodeSampledBitmapFromResource(InputStream inputStream, int reqWidth, int reqHeight) {

    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    byte[] buffer = new byte[1024];
    int len;
    try {
        while ((len = inputStream.read(buffer)) > -1) {
        baos.write(buffer, 0, len);
        }
        baos.flush();
        InputStream is1 = new ByteArrayInputStream(baos.toByteArray());
        InputStream is2 = new ByteArrayInputStream(baos.toByteArray());

        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(is1, null, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inJustDecodeBounds = false;
        options.inPreferredConfig = Bitmap.Config.ARGB_8888;
        return BitmapFactory.decodeStream(is2, null, options);

    } catch (Exception e) {
        e.printStackTrace();

        return null;
    }
}

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


    return inSampleSize;
}

bitmap = decodeSampledBitmapFromResource(inputStream, 600, 600); 

I am getting "Out of memory error on a 3250016 - byte allocation" in this line:

return BitmapFactory.decodeStream(is2, null, options);

It would seem to me that 3.2 MB is small enough to be allocated. Where am I going wrong? How can I solve this?

EDIT

After looking into this solution HERE by N-Joy it works fine with Required size 300 but my required size is 800, so i am still getting the error.

回答1:

The method decodeSampledBitmapFromResource is not memory efficient because it uses 3 streams: the ByteArrayOutputStream baos, ByteArrayInputStream is1 and ByteArrayInputStream is2, each of those stores the same stream data of the image (one byte array for each).

And when I test with my device (LG nexus 4) to decode an 2560x1600 image on SDcard to target size 800 it takes something like this:

03-13 15:47:52.557: E/DecodeBitmap(11177): dalvikPss (beginning) = 1780
03-13 15:47:53.157: E/DecodeBitmap(11177): dalvikPss (decoding) = 26393
03-13 15:47:53.548: E/DecodeBitmap(11177): dalvikPss (after all) = 30401 time = 999

We can see: too much memory allocated (28.5 MB) just to decode 4096000 a pixel image.

Solution: we read the InputStream and store the data directly into one byte array and use this byte array for the rest work.
Sample code:

public Bitmap decodeSampledBitmapFromResourceMemOpt(
            InputStream inputStream, int reqWidth, int reqHeight) {

        byte[] byteArr = new byte[0];
        byte[] buffer = new byte[1024];
        int len;
        int count = 0;

        try {
            while ((len = inputStream.read(buffer)) > -1) {
                if (len != 0) {
                    if (count + len > byteArr.length) {
                        byte[] newbuf = new byte[(count + len) * 2];
                        System.arraycopy(byteArr, 0, newbuf, 0, count);
                        byteArr = newbuf;
                    }

                    System.arraycopy(buffer, 0, byteArr, count, len);
                    count += len;
                }
            }

            final BitmapFactory.Options options = new BitmapFactory.Options();
            options.inJustDecodeBounds = true;
            BitmapFactory.decodeByteArray(byteArr, 0, count, options);

            options.inSampleSize = calculateInSampleSize(options, reqWidth,
                    reqHeight);
            options.inPurgeable = true;
            options.inInputShareable = true;
            options.inJustDecodeBounds = false;
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;

            int[] pids = { android.os.Process.myPid() };
            MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
            Log.e(TAG, "dalvikPss (decoding) = " + myMemInfo.dalvikPss);

            return BitmapFactory.decodeByteArray(byteArr, 0, count, options);

        } catch (Exception e) {
            e.printStackTrace();

            return null;
        }
    }

The method that does the calculation:

public void onButtonClicked(View v) {
        int[] pids = { android.os.Process.myPid() };
        MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);

        long startTime = System.currentTimeMillis();

        FileInputStream inputStream;
        String filePath = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/test2.png";
        File file = new File(filePath);
        try {
            inputStream = new FileInputStream(file);
//          mBitmap = decodeSampledBitmapFromResource(inputStream, 800, 800);
            mBitmap = decodeSampledBitmapFromResourceMemOpt(inputStream, 800,
                    800);
            ImageView imageView = (ImageView) findViewById(R.id.image);
            imageView.setImageBitmap(mBitmap);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }
        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (after all) = " + myMemInfo.dalvikPss
                + " time = " + (System.currentTimeMillis() - startTime));
    }

And the result:

03-13 16:02:20.373: E/DecodeBitmap(13663): dalvikPss (beginning) = 1823
03-13 16:02:20.923: E/DecodeBitmap(13663): dalvikPss (decoding) = 18414
03-13 16:02:21.294: E/DecodeBitmap(13663): dalvikPss (after all) = 18414 time = 917


回答2:

This is a common issue which user normally faces while playing with large bitmaps and there are lots a questions discussed on site, here, here, here and here and many more, even though user not able to manipulate the exact solution.

I stumbled upon a library sometime back which manages bitmaps smoothly and others links which I listed below. Hope this helps!

smoothie-Library

Android-BitmapCache

Android-Universal-Image-Loader Solution for OutOfMemoryError: bitmap size exceeds VM budget



回答3:

ARGB_8888 uses more memory as it takes Alpha color value so my suggestion is to use RGB_565 as stated HERE

Note: Quality will be little low compared to ARGB_8888.



回答4:

You're probably holding on to previous bitmap references. I'm guessing that you're executing this code several times and never executing bitmap.recycle(). Memory will inevitably run out.



回答5:

I had many problems with Bitmap memory usage.

Results:

  • Most devices have limited heap memory for graphics, most small devices are limited to 16MB for overall apps, not just your app
  • Use 4 bit or 8 bit or 16 bit bitmaps if applicable
  • Try to draw shapes from scratch, omit bitmaps if possible.


回答6:

Use WebView to dynamically load as many images as you like, it's built using NDK (low level) so has no GDI heap memory restrictions. It works smooth and fast :)



回答7:

Out of memory problems when decoding bitmaps are not often linked with the image size you are decoding. Of course if you try to open an image 5000x5000px you will fail with a OutOfMemoryError, but with the size of 800x800px it is totally reasonable and should work fine.

If your device is out of memory with a 3.2 MB image it's likely because you are leaking context somewhere in the app.

It's the first part of this post:

I guess problem is not in your layout, problem is somewhere else in your code. and probably you are leaking context somewhere.

What it means it's that you are using Activity Context in components that should not, preventing them to be garbage collected. Because there components often are held by activities, those activities are not GC and your java heap will grow very fast and your app will crash at one time or another.

As Raghunandan said, you will have to use MAT to find wich Activity/Component is held and remove the context leak.

The best way I found for now to detect context leak is orientation change. For example, rotate your ActivityMain multiple times, run MAT and check if you have only one instance of ActivityMain. If you have multiple ones (as much as rotation changes) it means there is a context leak.

I found years ago a good tutorial on using MAT. Maybe there is better one now.

Other posts on memory leaks:

Android - memory leak or?

Out of memory error on android emulator, but not on device



回答8:

Have a look at this video. http://www.youtube.com/watch?v=_CruQY55HOk. Do not use system.gc() as suggested in the video. Use a MAT Analyzer to find out memory leaks. The returned bitmap is too huge causing memory leak i guess.



回答9:

It seems you have large image to display.

You can download image and save to sdcard (example) then you can user this code to display image from sdcard.



回答10:

i also had same problem earlier.. and i have managed it by using this function where you can get scale as your required width and height.

private Bitmap decodeFile(FileInputStream f)
{
    try
    {
        //decode image size
        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        BitmapFactory.decodeStream(f,null,o);

        //Find the correct scale value. It should be the power of 2.
        final int REQUIRED_SIZE=70;
        int width_tmp=o.outWidth, height_tmp=o.outHeight;
        int scale=1;
        while(true)
        {
            if(width_tmp/2<REQUIRED_SIZE || height_tmp/2<REQUIRED_SIZE)
                break;
            width_tmp/=2;
            height_tmp/=2;
            scale*=2;
        }

        //decode with inSampleSize
        BitmapFactory.Options o2 = new BitmapFactory.Options();
        o2.inSampleSize=scale;
        return BitmapFactory.decodeStream(f, null, o2);
    } 
    catch (FileNotFoundException e) {}
    return null;
}

and refer Memory Leak Error Android and error in loading images to gridview android