Почему я НЕ получаю исключение нехватки памяти?

У меня есть изображение с высоким разрешением (2588*1603) в папке для рисования. Если я использую приведенный ниже код (1), чтобы установить его для imageView, я не получаю исключение OOM и изображение, назначенное как ожидалось:

public class MainActivity extends ActionBarActivity{


    private ImageView mImageView;

    int mImageHeight = 0;
    int mImageWidth  = 0;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

      mImageView = (ImageView) findViewById(R.id.imageView);
      mImageView.setScaleType(ScaleType.FIT_CENTER);

      BitmapFactory.Options sizeOption = new BitmapFactory.Options();
      sizeOption.inJustDecodeBounds = true;
      BitmapFactory.decodeResource(getResources(), R.drawable.a, sizeOption);
      mImageHeight = sizeOption.outHeight;
      mImageWidth  = sizeOption.outWidth; 

      mImageView.post(new Runnable() {
          @Override
          public void run() {
              try {
                BitmapRegionDecoder bmpDecoder = BitmapRegionDecoder
                          .newInstance(getResources().openRawResource(R.drawable.a),true);
            Rect rect = new Rect(0,0,mImageWidth, mImageHeight);
            BitmapFactory.Options options = new BitmapFactory.Options();
            options.inPreferredConfig = Bitmap.Config.ARGB_8888;
            options.inDensity = getResources().getDisplayMetrics().densityDpi;
            Bitmap bmp = bmpDecoder.decodeRegion(rect, options);

            mImageView.setImageBitmap(bmp);  

            } catch (NotFoundException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }   
          }
      });

    }
}

Обратите внимание, что размер прямоугольника точно соответствует размеру изображения.

Но если я использую другие методы, такие как, например, 2 или 3, я получаю OOM.

  2)  mImageView.setBackgroundResource(R.drawable.a);

  3) Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);
     mImageView.setImageBitmap(bmp);

Какая разница между 1 и 2,3?

(Я знаю, как решить ООМ, я просто хочу знать разницу)

4 ответа

Это источник BitmapRegionDecoder#decodeRegion:

public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
    checkRecycled("decodeRegion called on recycled region decoder");
    if (rect.left < 0 || rect.top < 0 || rect.right > getWidth()
            || rect.bottom > getHeight())
        throw new IllegalArgumentException("rectangle is not inside the image");
    return nativeDecodeRegion(mNativeBitmapRegionDecoder, rect.left, rect.top,
            rect.right - rect.left, rect.bottom - rect.top, options);
}

Как видите, он просто вызывает собственный метод. Я не понимаю достаточно C++, чтобы увидеть, масштабирует ли метод растровое изображение (в соответствии с вашим inDensity флаг).

Два других метода используют тот же собственный метод (nativeDecodeAsset) чтобы получить растровое изображение.

Номер 2 кэширует отрисовку и поэтому требует больше памяти.
После множества операций (проверка того, что растровое изображение уже предварительно загружено или обналичено и т. Д.), Он вызывает собственный метод для получения растрового изображения. Затем он кэширует отрисовку и устанавливает фоновое изображение.

Номер 3 довольно прост, он вызывает собственный метод после нескольких операций.


Вывод: мне трудно сказать, какой сценарий здесь применим, но он должен быть одним из этих двух.

  1. Ваша первая попытка уменьшить растровое изображение (inDensity флаг) и, следовательно, требует меньше памяти.
  2. Всем трем методам требуется более или менее одинаковый объем памяти, номера 2 и 3 - чуть больше. Ваше изображение использует ~16 МБ ОЗУ, что является максимальным размером кучи на некоторых телефонах. Номер 1 может быть ниже этого предела, в то время как другие два немного выше порога.

Я предлагаю вам отладить эту проблему. В вашем Манифесте установите android:largeHeap="true" чтобы получить больше памяти. Затем запустите 3 различные попытки и запишите размер кучи и количество байтов, выделенных битовой картой.

long maxMemory = Runtime.getRuntime().maxMemory();
long usedMemory = Runtime.getRuntime().totalMemory() - Runtime.getRuntime().freeMemory();
long freeMemory = maxMemory - usedMemory;
long bitmapSize = bmp.getAllocationByteCount();

Это даст вам лучший обзор.

Хорошо, вплоть до ядра, единственная разница между 1 и 2,3 в том, что 1 не поддерживает девять патчей и очищаемых. Так что, скорее всего, немного дополнительной памяти, выделенной для работы NinePatchPeeker во время декодирования, вызывает OOM в 2 и 3 (так как они используют один и тот же бэкэнд). В случае 1 он не выделяется.

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

  1. Вы не получаете исключение OOM из-за этого

    options.inPreferredConfig = Bitmap.Config.ARGB_8888;
    

Здесь уже дано

    public Bitmap.Config inPreferredConfig

Добавлено на уровне API 1

Если это не нуль, декодер попытается декодировать в эту внутреннюю конфигурацию. Если он равен нулю или запрос не может быть удовлетворен, декодер попытается выбрать наиболее подходящую конфигурацию на основе глубины экрана системы и характеристик исходного изображения, таких как наличие альфа-изображения на пиксель (для этого требуется конфигурация, которая также делает). Образ загружается с конфигурацией ARGB_8888 по умолчанию.

Слишком много деталей изображения приводит к нехватке памяти.

итоги: 1 использовать масштабированное растровое изображение; 2,3 загрузите полную детальную прорисовку (это приводит к нехватке памяти), затем измените размер и установите его для просмотра изображений.

1

Bitmap bmp = bmpDecoder.decodeRegion(rect, options);

конструктор (InputStream is, boolean isShareable) использует поток, который не исчерпает память.

используйте BitmapFactory.Options и BitmapRegionDecoder уменьшит размер растрового изображения.

См.: BitmapRegionDecoder будет рисовать запрошенный контент в предоставленном растровом изображении, вырезая его, если размер выходного содержимого (после масштабирования) больше, чем предоставленное растровое изображение. Предоставленная ширина, высота и Bitmap.Config растрового изображения не будут изменены.

2,3

Drawable d = mContext.getDrawable(mResource);
Bitmap bmp = BitmapFactory.decodeResource(getResources(), R.drawable.a);

нет опции масштабирования, вся картинка будет загружена в память

Извините за английский.

Может быть, поможет вам.

Другие вопросы по тегам