缩放和加载位图导致OOM (OutOfMemoryError) (Android)



我已经尝试了一千次,想自己解决这个问题,但没有任何成功。我已经记录并调试了应用程序,从我收集到的信息中,有一个用于动画的大型精灵表。png格式,大小为3000x1614像素。在精灵表中有10x6的精灵,这对动画来说是必不可少的。精灵之间没有任何间隙,以尽可能提高内存效率。

这是我的方法,我加载,解码和调整我的位图到用户的手机和我的手机的长宽比,我正在构建游戏:

public Pixmap newResizedPixmap(String fileName, PixmapFormat format, double Width, double Height) {
    // TODO Auto-generated method stub
    Config config = null;
    if (format == PixmapFormat.RGB565)
        config = Config.RGB_565;
    else if (format == PixmapFormat.ARGB4444)
        config = Config.ARGB_4444;
    else
        config = Config.ARGB_8888;
    Options options = new Options();
    options.inPreferredConfig = config;
    options.inPurgeable=true;
    options.inInputShareable=true;
    options.inDither=false;
    InputStream in = null;
    Bitmap bitmap = null;
    try {
        //OPEN FILE INTO INPUTSTREAM
        in = assets.open(fileName);
        //DECODE INPUTSTREAM
        bitmap = BitmapFactory.decodeStream(in, null, options);
        if (bitmap == null)
            throw new RuntimeException("Couldn't load bitmap from asset '"+fileName+"'");
    } catch (IOException e) {
        throw new RuntimeException("Couldn't load bitmap from asset '"+fileName+"'");
    } finally {
        if (in != null) {
            try {
                in.close();
            } catch (IOException e) {
            }
        }
    }
    if (bitmap.getConfig() == Config.RGB_565)
        format = PixmapFormat.RGB565;
    else if (bitmap.getConfig() == Config.ARGB_4444)
        format = PixmapFormat.ARGB4444;
    else
        format = PixmapFormat.ARGB8888;
    //THIS IS WHERE THE OOM HAPPENS
    return new AndroidPixmap(Bitmap.createScaledBitmap(bitmap, (int)(Width+0.5f), (int)(Height+0.5f), true), format);
}

这是错误的LogCat输出:

05-07 12:57:18.570: E/dalvikvm-heap(636): Out of memory on a 29736016-byte allocation.
05-07 12:57:18.570: I/dalvikvm(636): "Thread-89" prio=5 tid=18 RUNNABLE
05-07 12:57:18.580: I/dalvikvm(636):   | group="main" sCount=0 dsCount=0 obj=0x414a3c78 self=0x2a1b1818
05-07 12:57:18.580: I/dalvikvm(636):   | sysTid=669 nice=0 sched=0/0 cgrp=apps handle=705796960
05-07 12:57:18.590: I/dalvikvm(636):   | schedstat=( 14462294890 67436634083 2735 ) utm=1335 stm=111 core=0
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.nativeCreate(Native Method)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createBitmap(Bitmap.java:586)
05-07 12:57:18.590: I/dalvikvm(636):   at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:466)
05-07 12:57:18.590: I/dalvikvm(636):   at com.NAME.framework.impl.AndroidGraphics.newResizedPixmap(AndroidGraphics.java:136)
05-07 12:57:18.590: I/dalvikvm(636):   at com.NAME.GAME.GameScreen.<init>(GameScreen.java:121)
05-07 12:57:18.600: I/dalvikvm(636):   at com.NAME.GAME.CategoryScreen.update(CategoryScreen.java:169)
05-07 12:57:18.600: I/dalvikvm(636):   at com.NAME.framework.impl.AndroidFastRenderView.run(AndroidFastRenderView.java:34)
05-07 12:57:18.600: I/dalvikvm(636):   at java.lang.Thread.run(Thread.java:856)
05-07 12:57:18.620: I/Choreographer(636): Skipped 100 frames!  The application may be doing too much work on its main thread.
05-07 12:57:18.670: W/dalvikvm(636): threadid=18: thread exiting with uncaught exception (group=0x40a13300)
05-07 12:57:18.720: E/AndroidRuntime(636): FATAL EXCEPTION: Thread-89
05-07 12:57:18.720: E/AndroidRuntime(636): java.lang.OutOfMemoryError
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.nativeCreate(Native Method)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createBitmap(Bitmap.java:640)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createBitmap(Bitmap.java:586)
05-07 12:57:18.720: E/AndroidRuntime(636):  at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:466)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.framework.impl.AndroidGraphics.newResizedPixmap(AndroidGraphics.java:136)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.GAME.GameScreen.<init>(GameScreen.java:121)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.GAME.CategoryScreen.update(CategoryScreen.java:169)
05-07 12:57:18.720: E/AndroidRuntime(636):  at com.NAME.framework.impl.AndroidFastRenderView.run(AndroidFastRenderView.java:34)
05-07 12:57:18.720: E/AndroidRuntime(636):  at java.lang.Thread.run(Thread.java:856)
05-07 12:57:18.847: D/Activity:(636): Pausing...

这是使用DDMS的内存泄漏报告:

问题疑点1

"com.NAME.framework.impl.AndroidPixmap"的一个实例由"dalvik.system"加载。PathClassLoader @ 0x41a06f90"占用12393680(54,30%)字节。的一个实例中积累了内存"byte[]"被".

关键字dalvik.system.PathClassLoader @ 0x41a06f90com.NAME.framework.impl.AndroidPixmap byte []

详情»

问题疑点2

类"android.content.res. "由"加载的资源"占用4 133 808(18.11%)字节。记忆是积累起来的在"java.lang. net"的一个实例中。对象[]"加载".

关键字. lang。对象[]android.content.res.Resources

详情»

问题疑点3

8个"android.graphics。由"加载的位图"占用2 696 680(11.82%)字节。

最大实例:•android.graphics。位图@ 0x41462ba0 - 1 048 656(4 59%)字节。•android.graphics。位图@ 0x41a08cf0 - 768 064(3 37%)字节。•android.graphics。位图@ 0x41432990 - 635 872(2 79%)字节。

关键字android.graphics.Bitmap

详情»


附加信息:我使用RGB565或ARGB4444作为图像的配置。我希望使用ARGB8888的图像与梯度,但它只是占用太多的内存。另一件需要注意的事情是,当我不再需要位图时,我确实会回收它们。

另一个似乎消耗大量内存的东西是我的Assets类,它包含游戏使用的所有"Pixmaps"。从资产中加载的位图存储在这些"像素图"中,当不再需要时删除(即位图)。有没有更好的方法来存储这些对象?请告诉我:

public static Pixmap image1;
public static Pixmap image2;
public static Pixmap image3;
public static Pixmap image4;
public static Pixmap image5;
public static Pixmap image6;
public static Pixmap image7;
public static Pixmap image8;
public static Pixmap image9;
public static Pixmap image10;
public static Pixmap image11;
...

另一件需要注意的事情是,OOM只发生在分辨率高于我自己的设备上(800x480),这是因为位图被缩放以使应用适合更大的设备。

还请注意,图像是在画布上绘制的,因为我正在开发的是一款游戏,我对OpenGL不是很有经验。


编辑#1:只是为了确保你知道我的问题是什么

我得到OOM在以下行:

Bitmap.createScaledBitmap(bitmap, (int)(Width), (int)(Height), true)

我急需帮助!这是我发布这款游戏的最后一个障碍!


编辑#2:我试过的新方法都不起作用

我尝试在使用它们之前将解码的位图添加到LruCache。我还尝试了一种方法,它在临时文件中创建位图的地图,然后再次加载它。这样的:

// Get memory class of this device, exceeding this amount will throw an
// OutOfMemory exception.
final int memClass = ((ActivityManager) getSystemService(
        Context.ACTIVITY_SERVICE)).getMemoryClass();
// Use 1/8th of the available memory for this memory cache.
final int cacheSize = 1024 * 1024 * memClass / 8;
mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    @Override
    protected int sizeOf(String key, Bitmap bitmap) {
        // The cache size will be measured in kilobytes rather than
        // number of items.
        return (bitmap.getRowBytes() * bitmap.getHeight()) / 1024;
    }
};

加载位图

        //Copy the byte to the file
//Assume source bitmap loaded using options.inPreferredConfig = Config.ARGB_8888;
FileChannel channel = randomAccessFile.getChannel();
MappedByteBuffer map = null;
try {
    map = channel.map(MapMode.READ_WRITE, 0, width*height*4);
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
bitmap.copyPixelsToBuffer(map);
//recycle the source bitmap, this will be no longer used.
bitmap.recycle();
//Create a new bitmap to load the bitmap again.
bitmap = Bitmap.createBitmap(width, height, config);
map.position(0);
//load it back from temporary 
bitmap.copyPixelsFromBuffer(map);
//close the temporary file and channel , then delete that also
try {
    channel.close();
    randomAccessFile.close();
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}
((AndroidGame) activity).addBitmapToMemoryCache(""+fileName, Bitmap.createScaledBitmap(bitmap, (int)(Width), (int)(Height), true));
return new AndroidPixmap(((AndroidGame) activity).getBitmapFromMemCache(""+fileName), format);

编辑# 3:如果你不知道…

我是放大位图,而不是缩小它们,所以inSampleSize在这种情况下不会帮助我。我现在可以说,我已经试过inSampleSize了这让一切都很模糊,我甚至看不清东西是什么,这就是我把inSampleSize设为2的时候!


那么,我应该怎么做来解决这个问题呢?我如何能够在没有OOM的情况下加载大量缩放位图,同时保持它们的质量?

这是一种解决方法,但这将比直接缩放位图花费更长的时间。

  1. 首先,将原始位图解码为原始大小
  2. 分割成小位图,在这种情况下是10位图与宽度=originalWidth/10和height = originalHeight。以及每个位图的宽度其中,缩放到target/10的大小,然后保存到文件中。(我们做的这一步一步来,每一步后释放内存。
  3. 释放原始位图
  4. 创建一个目标大小的新位图。
  5. 解码之前保存在文件中的每个位图并绘制到新的位图上。商店
方法是这样的:
public Bitmap newResizedPixmapWorkAround(Bitmap.Config format,
            double width, double height) {
        int[] pids = { android.os.Process.myPid() };
        MemoryInfo myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (beginning) = " + myMemInfo.dalvikPss);
        Config config = null;
        if (format == Bitmap.Config.RGB_565) {
            config = Config.RGB_565;
        } else if (format == Bitmap.Config.ARGB_4444) {
            config = Config.ARGB_4444;
        } else {
            config = Config.ARGB_8888;
        }
        Options options = new Options();
        options.inPreferredConfig = config;
        options.inPurgeable = true;
        options.inInputShareable = true;
        options.inDither = false;
        InputStream in = null;
        Bitmap bitmap = null;
        try {
            // OPEN FILE INTO INPUTSTREAM
            String path = Environment.getExternalStorageDirectory()
                    .getAbsolutePath();
            path += "/sprites.png";
            File file = new File(path);
            in = new FileInputStream(file);
            // DECODE INPUTSTREAM
            bitmap = BitmapFactory.decodeStream(in, null, options);
            if (bitmap == null) {
                Log.e(TAG, "bitmap == null");
            }
        } catch (IOException e) {
            Log.e(TAG, "IOException " + e.getMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                }
            }
        }
        if (bitmap.getConfig() == Config.RGB_565) {
            format = Bitmap.Config.RGB_565;
        } else if (bitmap.getConfig() == Config.ARGB_4444) {
            format = Bitmap.Config.ARGB_4444;
        } else {
            format = Bitmap.Config.ARGB_8888;
        }
        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (before separating) = " + myMemInfo.dalvikPss);
        // Create 10 small bitmaps and store into files.
        // After that load each of them and append to a large bitmap
        int desSpriteWidth = (int) (width + 0.5) / 10;
        int desSpriteAppend = (int) (width + 0.5) % 10;
        int desSpriteHeight = (int) (height + 0.5);
        int srcSpriteWidth = bitmap.getWidth() / 10;
        int srcSpriteHeight = bitmap.getHeight();
        Rect srcRect = new Rect(0, 0, srcSpriteWidth, srcSpriteHeight);
        Rect desRect = new Rect(0, 0, desSpriteWidth, desSpriteHeight);
        String pathPrefix = Environment.getExternalStorageDirectory()
                .getAbsolutePath() + "/sprites";
        for (int i = 0; i < 10; i++) {
            Bitmap sprite;
            if (i < 9) {
                sprite = Bitmap.createBitmap(desSpriteWidth, desSpriteHeight,
                        format);
                srcRect.left = i * srcSpriteWidth;
                srcRect.right = (i + 1) * srcSpriteWidth;
            } else { // last part
                sprite = Bitmap.createBitmap(desSpriteWidth + desSpriteAppend,
                        desSpriteHeight, format);
                desRect.right = desSpriteWidth + desSpriteAppend;
                srcRect.left = i * srcSpriteWidth;
                srcRect.right = bitmap.getWidth();
            }
            Canvas canvas = new Canvas(sprite);
            // NOTE: if using this drawBitmap method of canvas do not result
            // good quality scaled image, you can try create tile image with the
            // same size original then use Bitmap.createScaledBitmap()
            canvas.drawBitmap(bitmap, srcRect, desRect, null);
            String path = pathPrefix + i + ".png";
            File file = new File(path);
            try {
                if (file.exists()) {
                    file.delete();
                }
                file.createNewFile();
                FileOutputStream fos = new FileOutputStream(file);
                sprite.compress(Bitmap.CompressFormat.PNG, 100, fos);
                fos.flush();
                fos.close();
                sprite.recycle();
            } catch (FileNotFoundException e) {
                Log.e(TAG,
                        "FileNotFoundException  at tile " + i + " "
                                + e.getMessage());
            } catch (IOException e) {
                Log.e(TAG, "IOException  at tile " + i + " " + e.getMessage());
            }
        }
        bitmap.recycle();
        bitmap = Bitmap.createBitmap((int) (width + 0.5f),
                (int) (height + 0.5f), format);
        Canvas canvas = new Canvas(bitmap);
        desRect = new Rect(0, 0, 0, bitmap.getHeight());
        for (int i = 0; i < 10; i++) {
            String path = pathPrefix + i + ".png";
            File file = new File(path);
            try {
                FileInputStream fis = new FileInputStream(file);
                // DECODE INPUTSTREAM
                Bitmap sprite = BitmapFactory.decodeStream(fis, null, options);
                desRect.left = desRect.right;
                desRect.right = desRect.left + sprite.getWidth();
                canvas.drawBitmap(sprite,  null, desRect, null);
                sprite.recycle();
            } catch (IOException e) {
                Log.e(TAG, "IOException  at tile " + i + " " + e.getMessage());
            }
        }
        myMemInfo = mAM.getProcessMemoryInfo(pids)[0];
        Log.e(TAG, "dalvikPss (before scaling) = " + myMemInfo.dalvikPss);
        // THIS IS WHERE THE OOM HAPPENS
        return bitmap;
    }

我已经用我的手机测试过了,当缩放到1.5(原始方法将运行到OOM)时,它没有OOM运行

为什么要放大位图?画的时候按比例缩放。

3000x1614还挺大的,如果能放下支持<= 2.2;http://developer.android.com/reference/android/graphics/BitmapRegionDecoder.html可以加载位图的特定部分(精灵)。

你正在传递新的位图到AndroidPixmap的构造函数中,看起来你在保持对它们的静态引用-你到底是如何回收它们的?

作为逃避,您可以在清单中使用<application ... android:largeHeap="true" ...>请求更多内存;http://developer.android.com/guide/topics/manifest/application-element.html#largeHeap我相信这需要> =3.0

只是为了帮助(但不是真正的解决方案),你读过这个吗:

http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

:

http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

也许可以帮助你!如果您测试交付的样例项目并使用大图更改URL路径!

并且,当我有工作的时候,我读了这个:

http://twigstechtips.blogspot.com/2011/10/android-resize-bitmap-image-while.html

试试这个函数

void scaleBitmap()
{

        BitmapFactory.Options o = new BitmapFactory.Options();
        o.inJustDecodeBounds = true;
        o.inPurgeable = true;
        o.inScaled = true;
        BitmapFactory.decodeStream(new FileInputStream(f), null, o);
        int width_temp = o.outWidth, height_temp = o.outHeight;
        int scale = 1;
        while (true) {
                if (width_temp / 2 < 80 || height_temp / 2 < 80)
                    break;
                width_temp /= 2;
                height_temp /= 2;
                scale *= 2;
            }
            BitmapFactory.Options o2 = new BitmapFactory.Options();
            o2.inSampleSize = scale;
            return BitmapFactory.decodeStream(new FileInputStream(f), null,
                    o2);
}

将您的图像转换为位图对象。并且使用Bitmap类的api来缩放图像,以避免内存不足错误。

       Bitpmap bm = BitmapFactory.decode ("image file path");
       bm.setDensity(integer);
       Bitmap bm1 = Bitmap.createScaledBitmap (bm, width, height, false);
       imageView.setImageBitmap(bm1);

尝试设置width和height的值,这样就不会泄漏内存。希望这能帮到你。

使用-Xmx VM参数允许JVM使用更多内存。

例如,允许JVM使用1gb (1024mb)内存:

java -Xmx1024m

使用任意图像上传器并在demo

中传递图像url

1) Nostra的通用图像加载器。

2)费多的懒人清单。

;3) Novoda的ImageLoader.

最新更新