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, поэтому я все еще получаю ошибку.
Ответы
Ответ 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