Android создает битмап + обрез, вызывающий ошибку OutOfMemory (в конечном итоге)

49
3

Перед отправкой на удаленный сервер я делаю 3 снимка в своем приложении. Результатом является байтовый массив. Я в настоящее время преобразовываю этот byteArray в растровое изображение, выполняя обрезку на нем (обрезая центральный квадрат). В конечном итоге у меня закончилась нехватка памяти (то есть после выхода приложения обратно, выполнение тех же шагов). Я пытаюсь повторно использовать объект растрового изображения, используя BitmapFactory.Options, как указано в руководстве разработчика Android.

https://www.youtube.com/watch?v=_ioFW3cyRV0&list=LLntRvRsglL14LdaudoRQMHg&index=2

а также

https://www.youtube.com/watch?v=rsQet4nBVi8&list=LLntRvRsglL14LdaudoRQMHg&index=3

Это функция, которую я вызываю, когда я сохраняю изображение, сделанное камерой.

public void saveImageToDisk(Context context, byte[] imageByteArray, String photoPath, BitmapFactory.Options options) {
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(imageByteArray, 0, imageByteArray.length, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
int dimension = getSquareCropDimensionForBitmap(imageWidth, imageHeight);
Log.d(TAG, "Width : " + dimension);
Log.d(TAG, "Height : " + dimension);
//bitmap = cropBitmapToSquare(bitmap);
options.inJustDecodeBounds = false;

Bitmap bitmap = BitmapFactory.decodeByteArray(imageByteArray, 0,
imageByteArray.length, options);
options.inBitmap = bitmap;

bitmap = ThumbnailUtils.extractThumbnail(bitmap, dimension, dimension,
ThumbnailUtils.OPTIONS_RECYCLE_INPUT);
options.inSampleSize = 1;

Log.d(TAG, "After square crop Width : " + options.inBitmap.getWidth());
Log.d(TAG, "After square crop Height : " + options.inBitmap.getHeight());
byte[] croppedImageByteArray = convertBitmapToByteArray(bitmap);
options = null;

File photo = new File(photoPath);
if (photo.exists()) {
photo.delete();
}

try {
FileOutputStream e = new FileOutputStream(photo.getPath());
BufferedOutputStream bos = new BufferedOutputStream(e);
bos.write(croppedImageByteArray);
bos.flush();
e.getFD().sync();
bos.close();
} catch (IOException e) {
}

}

public int getSquareCropDimensionForBitmap(int width, int height) {
//If the bitmap is wider than it is tall
//use the height as the square crop dimension
int dimension;
if (width >= height) {
dimension = height;
}
//If the bitmap is taller than it is wide
//use the width as the square crop dimension
else {
dimension = width;
}
return dimension;
}

public Bitmap cropBitmapToSquare(Bitmap source) {
int h = source.getHeight();
int w = source.getWidth();
if (w >= h) {
source = Bitmap.createBitmap(source, w / 2 - h / 2, 0, h, h);
} else {
source = Bitmap.createBitmap(source, 0, h / 2 - w / 2, w, w);
}
Log.d(TAG, "After crop Width : " + source.getWidth());
Log.d(TAG, "After crop Height : " + source.getHeight());

return source;
}

Как правильно переработать или повторно использовать растровые изображения, потому что на данный момент я получаю ошибки OutOfMemory?

ОБНОВИТЬ :

После внедрения решения Colin. Я запускаю ArrayIndexOutOfBoundsException.

Мои журналы ниже

08-26 01:45:01.895    3600-3648/com.test.test E/AndroidRuntime﹕ FATAL EXCEPTION: pool-3-thread-1
Process: com.test.test, PID: 3600
java.lang.ArrayIndexOutOfBoundsException: length=556337; index=556337
at com.test.test.helpers.Utils.test(Utils.java:197)
at com.test.test.fragments.DemoCameraFragment.saveImageToDisk(DemoCameraFragment.java:297)
at com.test.test.fragments.DemoCameraFragment_.access$101(DemoCameraFragment_.java:30)
at com.test.test.fragments.DemoCameraFragment_$5.execute(DemoCameraFragment_.java:159)
at org.androidannotations.api.BackgroundExecutor$Task.run(BackgroundExecutor.java:401)
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:422)
at java.util.concurrent.FutureTask.run(FutureTask.java:237)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$201(ScheduledThreadPoolExecutor.java:152)
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:265)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1112)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:587)
at java.lang.Thread.run(Thread.java:818)

PS: Я думал об обрезке byteArrays раньше, но я не знал, как его реализовать.

спросил(а) 2015-08-21T00:37:00+03:00 4 года, 6 месяцев назад
1
Решение
69

На самом деле вам не нужно делать какое-либо преобразование в растровые изображения. Помните, что ваши данные растрового изображения RGBA_8888 отформатированы. Это означает, что каждые 4 смежных байта представляют один пиксель. Как таковой:

// helpers to make sanity
int halfWidth = imgWidth >> 1;
int halfHeight = imgHeight >> 1;
int halfDim = dimension >> 1;

// get our min and max crop locations
int minX = halfWidth - halfDim;
int minY = halfHeight - halfDim;
int maxX = halfWidth + halfDim;
int maxY = halfHeight + halfDim;

// allocate our thumbnail; It WxH*(4 bits per pixel)
byte[] outArray = new byte[dimension * dimension * 4]

int outPtr = 0;
for(int y = minY; y< maxY; y++)
{
for(int x = minX; x < maxX; x++)
{
int srcLocation = (y * imgWidth) + (x * 4);
outArray[outPtr + 0] = imageByteArray[srcLocation +0]; // read R
outArray[outPtr + 1] = imageByteArray[srcLocation +1]; // read G
outArray[outPtr + 2] = imageByteArray[srcLocation +2]; // read B
outArray[outPtr + 3] = imageByteArray[srcLocation +3]; // read A
outPtr+=4;
}
}
//outArray now contains the cropped pixels.

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

== EDIT:

На самом деле; Вышеупомянутый алгоритм предполагает, что ваши входные данные являются необработанными данными RGBA_8888. Но это похоже на то, что ваш массив байтов ввода - это кодированные данные JPG. Таким образом, ваш 2-й декодерByteArray фактически декодирует ваш JPG файл в формате RGBA_8888. Если это так, правильная вещь для повторной калибровки заключается в использовании методов, описанных в разделе " Самый эффективный способ изменения размера растровых изображений на андроиде? ", Поскольку вы работаете с закодированными данными.

ответил(а) 2015-08-25T16:04:00+03:00 4 года, 6 месяцев назад
35

Попробуйте установить все больше и больше переменных в null - это помогает восстановить эту память;

после

 byte[] croppedImageByteArray = convertBitmapToByteArray(bitmap);

делать:

bitmap= null;

после

 FileOutputStream e = new FileOutputStream(photo.getPath());

делать

photo = null;

и после

 try {
FileOutputStream e = new FileOutputStream(photo.getPath());
BufferedOutputStream bos = new BufferedOutputStream(e);
bos.write(croppedImageByteArray);
bos.flush();
e.getFD().sync();
bos.close();
} catch (IOException e) {
}

делать:

e = null;
bos = null;

Редактировать # 1

Если это не поможет, ваше единственное реальное решение на самом деле использует монитор памяти. Узнать больше здесь и здесь

Ps. есть еще одно очень темное решение, очень темное решение. Только для тех, кто знает, как перемещаться по темным углам ofheapmemory. Но вам придется следовать этому пути самостоятельно.

ответил(а) 2015-08-21T01:11:00+03:00 4 года, 6 месяцев назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема