Подтвердить что ты не робот

BitmapFactory.decodeStream из памяти, несмотря на уменьшение размера выборки

Я прочитал много связанных сообщений о проблемах с распределением памяти с расшифровкой растровых изображений, но я все еще не могу найти решение следующей проблемы даже после использования кода, представленного на официальном сайте.

Вот мой код:

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

Я получаю сообщение "Ошибка вне памяти при распределении по 3250016 байт" в этой строке:

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

Мне кажется, что 3,2 МБ достаточно мало, чтобы выделяться. Где я иду не так? Как я могу это решить?

ИЗМЕНИТЬ

Изучив это решение ЗДЕСЬ от N-Joy, он отлично работает с обязательным размером 300, но мой размер - 800, поэтому я все еще получаю ошибку.

4b9b3361

Ответ 1

Метод decodeSampledBitmapFromResource не эффективен с точки зрения памяти, поскольку он использует 3 потока: ByteArrayOutputStream baos, ByteArrayInputStream is1 и ByteArrayInputStream is2, каждый из них хранит одни и те же данные потока изображения (один байт-массив для каждого).

И когда я тестирую свое устройство (LG nexus 4), чтобы декодировать изображение 2560x1600 на SD-карте на целевой размер 800, требуется следующее:

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

Мы можем видеть: слишком много выделенной памяти (28,5 МБ) просто для декодирования 4096000 пиксельных изображений.

Решение: мы читаем InputStream и храним данные непосредственно в один массив байтов и используем этот массив байтов для остальной работы.
Пример кода:

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

Метод, который выполняет расчет:

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

И результат:

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

Это обычная проблема, с которой обычно сталкивается пользователь во время игры с большими растровыми изображениями, и есть много вопросов, обсуждаемых на сайте, здесь, здесь и здесь и многое другое, хотя пользователь не в состоянии манипулировать точным решением.

Я наткнулся на библиотеку когда-нибудь, которая плавно управляет растровыми изображениями и другими ссылками, которые я перечислял ниже. Надеюсь это поможет!

smoothie-Library

Android-BitmapCache

Android-Universal-Image-Loader Решение для OutOfMemoryError: размер растрового изображения превышает бюджет VM

Ответ 3

ARGB_8888 использует больше памяти, так как требует значения цвета Alpha, поэтому мое предложение заключается в использовании RGB_565, как указано ЗДЕСЬ

Примечание. Качество будет немного низким по сравнению с ARGB_8888.

Ответ 4

Вероятно, вы держитесь за предыдущие ссылки на растровые изображения. Я предполагаю, что вы выполняете этот код несколько раз и никогда не выполняете bitmap.recycle(). Память неизбежно закончится.

Ответ 5

У меня было много проблем с использованием памяти Bitmap.

Результаты:

  • Большинство устройств имеют ограниченную память кучи для графики, большинство небольших устройств ограничено 16 МБ для общих приложений, а не только для вашего приложения.
  • Используйте 4-битные или 8-битные или 16-битные растровые изображения, если применимо
  • Попробуйте нарисовать фигуры с нуля, опустите растровые изображения, если это возможно.

Ответ 6

Используйте WebView, чтобы динамически загружать как можно больше изображений, созданных с использованием NDK (низкий уровень), поэтому не имеет ограничений памяти кучи GDI. Он работает гладко и быстро:)

Ответ 7

Проблемы с памятью при декодировании растровых изображений не часто связаны с размером изображения, которое вы декодируете. Конечно, если вы попытаетесь открыть изображение 5000x5000px, вы потерпите неудачу с помощью OutOfMemoryError, но с размером 800x800 пикселей это будет вполне разумно и должно работать нормально.

Если на вашем устройстве не хватает памяти с 3,2 Мбайтом изображения, это вероятно из-за того, что вы пропускаете контекст где-то в приложении.

Это первая часть этого сообщения:

Я думаю, проблема не в вашем макете, проблема в другом месте ваш код. и возможно, вы утечка контекста где-то.

Что это значит, что вы используете контекст активности в компонентах, которые не должны, не позволяя им собирать мусор. Поскольку компоненты часто выполняются по видам деятельности, эти действия не являются GC, и ваша куча java будет расти очень быстро, и ваше приложение будет сбой в тот или иной момент.

Как сказал Рагхунандан, вам нужно будет использовать MAT, чтобы найти, какой Activity/Component удерживается и удалить утечку контекста.

Лучший способ найти утечку контекста - изменение ориентации. Например, несколько раз вращайте ActivityMain, запустите MAT и проверьте, есть ли у вас только один экземпляр ActivityMain. Если у вас несколько (столько же, сколько и изменение вращения), это означает, что есть утечка контекста.

Я нашел много лет назад хороший учебник по использованию MAT. Может быть, теперь лучше.

Другие сообщения об утечках памяти:

Android - утечка памяти или?

Ошибка вне памяти в эмуляторе Android, но не на устройстве

Ответ 8

Посмотрите это видео. http://www.youtube.com/watch?v=_CruQY55HOk. Не используйте system.gc(), как указано в видео. Используйте анализатор MAT, чтобы узнать утечки памяти. Возвращаемое растровое изображение слишком велико, что, по-моему, вызывает утечку памяти.

Ответ 9

Кажется, у вас есть большое изображение для отображения.

Вы можете загрузить изображение и сохранить на sdcard (example), тогда вы можете это код для отображения изображения с SD-карты.

Ответ 10

У меня также была такая же проблема раньше.. и я справился с ней, используя эту функцию, где вы можете получить масштаб как необходимую ширину и высоту.

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

и обратитесь Ошибка утечки памяти и при загрузке изображений в gridview android