Android создает битмап + обрез, вызывающий ошибку OutOfMemory (в конечном итоге)
Перед отправкой на удаленный сервер я делаю 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 раньше, но я не знал, как его реализовать.
На самом деле вам не нужно делать какое-либо преобразование в растровые изображения. Помните, что ваши данные растрового изображения 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. Если это так, правильная вещь для повторной калибровки заключается в использовании методов, описанных в разделе " Самый эффективный способ изменения размера растровых изображений на андроиде? ", Поскольку вы работаете с закодированными данными.
Попробуйте установить все больше и больше переменных в 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. Но вам придется следовать этому пути самостоятельно.