【问题标题】:Android create Bitmap + crop causing OutOfMemory error(eventually)Android 创建位图 + 裁剪导致 OutOfMemory 错误(最终)
【发布时间】:2015-11-14 17:17:41
【问题描述】:

在将应用上传到远程服务器之前,我在应用中拍摄了 3 张照片。输出是一个字节数组。我目前正在将此 byteArray 转换为位图,对其进行裁剪(裁剪中心正方形)。我最终耗尽了内存(即在退出应用程序后返回,执行相同的步骤)。我正在尝试使用 Android 开发指南中提到的 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 错误,我该如何正确回收或重复使用位图?

更新:

实施 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)

P.S : 之前有想过裁剪 byteArrays,但是不知道怎么实现。

【问题讨论】:

    标签: android bitmap out-of-memory crop


    【解决方案1】:

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

    附言。还有另一种很暗的溶液,很暗的溶液。仅适用于那些知道如何通过堆内存的黑暗角落导航的人。但是你必须自己走这条路。

    【讨论】:

    • 这对@Adam 的问题没有帮助
    【解决方案2】:

    实际上,您不需要对位图进行任何转换。 请记住,您的位图图像数据是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?”中描述的技术,因为您正在处理编码数据。

    【讨论】:

    • 嗨柯尔特。我在循环中遇到了 java.lang.ArrayIndexOutOfBoundsException。我已经编辑了问题并添加了相同的日志。你能澄清一下 imgWidth 和 width 是什么吗?我将宽度作为我们解码时得到的总位图宽度。我假设 imgWidth 是相同的。 outPtr 的值也不会改变。不应该有基于迭代的增量吗?非常感谢您的回复!
    • 更新了代码以向前移动 outptr,并使用更准确的内容更新 srclocation。 ArrayIndexOutOfBoundsException 仅表示 outArray 或 imageByteArray 在大于其长度的位置被索引。
    • byteArray其实就是camera(Commonsware的CWAC Camera)的输出。相机已将图片格式设置为 256,即 JPEG。 JPEG 没有透明度(alpha 通道)。那么这有关系吗?
    • 当图像从解码过程加载到内存中时,它总是转换为 RGBA8888,无论它是否具有透明度。
    • 谢谢柯尔特。关于为什么我让索引超出范围异常的任何想法?这是我无法克服的障碍。
    猜你喜欢
    • 1970-01-01
    • 2016-08-28
    • 2016-04-01
    • 1970-01-01
    • 2015-12-11
    • 1970-01-01
    • 1970-01-01
    • 2015-01-25
    • 1970-01-01
    相关资源
    最近更新 更多