Почему я НЕ получаю исключение нехватки памяти?
У меня есть изображение с высоким разрешением (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 довольно прост, он вызывает собственный метод после нескольких операций.
Вывод: мне трудно сказать, какой сценарий здесь применим, но он должен быть одним из этих двух.
- Ваша первая попытка уменьшить растровое изображение (
inDensity
флаг) и, следовательно, требует меньше памяти. - Всем трем методам требуется более или менее одинаковый объем памяти, номера 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 - нет.
Вы не получаете исключение 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);
нет опции масштабирования, вся картинка будет загружена в память
Извините за английский.
Может быть, поможет вам.