BitmapFactory.decodeStream out of memory despite u

2019-01-08 09:55发布

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.

10条回答
贪生不怕死
2楼-- · 2019-01-08 10:05

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
查看更多
戒情不戒烟
3楼-- · 2019-01-08 10:05

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.

查看更多
爱情/是我丢掉的垃圾
4楼-- · 2019-01-08 10:07

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.

查看更多
小情绪 Triste *
5楼-- · 2019-01-08 10:09

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

查看更多
Explosion°爆炸
6楼-- · 2019-01-08 10:12

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

查看更多
放我归山
7楼-- · 2019-01-08 10:15

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.

查看更多
登录 后发表回答