Android创建Bitmap +裁剪导致OutOfMemory错误(最终)

时间:2015-08-20 21:37:46

标签: android bitmap out-of-memory crop

我在我的应用中拍了3张照片,然后再将它上传到远程服务器。输出是byteArray。我目前正在将此byteArray转换为位图,在其上执行裁剪(裁剪中心方块)。我最终耗尽内存(即退出应用程序后退出,执行相同的步骤)。我正在尝试使用Android dev指南中提到的BitmapFactory.Options重用位图对象

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错误?

更新:

实施科林的解决方案后。我遇到了一个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)

P.S:之前我曾考虑过裁剪byteArrays,但我不知道如何实现它。

2 个答案:

答案 0 :(得分:3)

实际上,您不需要对位图进行任何转换。 请记住,您的位图图像数据格式为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's 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.

最终结果是你可以通过复制你正在寻找的像素来手工裁剪,而不是分配一个新的位图对象,然后将其转换回字节数组。

==编辑:

实际上;上述算法假设您的输入数据是原始RGBA_8888像素数据。但听起来,您的输入字节数组是编码的JPG数据。因此,您的第二个decodeByteArray实际上是将您的JPG文件解码为RGBA_8888格式。如果是这种情况,那么重新调整大小的正确方法是使用“Most memory efficient way to resize bitmaps on android?”中描述的技术,因为您正在使用编码数据。

答案 1 :(得分:0)

尝试将越来越多的变量设置为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

如果这无法提供帮助,那么您实际使用内存监视器的唯一真正解决方案。要了解详情,请转到herehere

聚苯乙烯。还有另一种非常暗的解决方案,非常暗的解决方案只有那些知道如何通过记忆的黑暗角落导航的人。但是你必须自己遵循这条道路。