Android Java 加载 spritsheets 内存不足异常

Android Java out of memory exception loading spritsheets

我想在 android 设备上制作虚拟宠物,但我在加载所有 spritesheet(这些是 .png 文件)时遇到问题。我可以加载一定数量的纸张而不会崩溃,但不是全部。当然这是由于内存不够。每张图片都小于 650 kB(每张图片的分辨率大约为 200x6300),所以我想我做错了什么可怕的事情。我的问题:"How to load all these images withouth crashing due to an Out of Memory Exception?"

我在单例中加载图像 class。这个 class 负责应用程序需要的所有资源。这是我用来加载图像的代码。

public final class ResourceManager {
    public static ResourceManager INSTANCE;
    private int screenWidth, screenHeight;
    private float dpscreenWidth, dpscreenHeight;
    private DisplayMetrics metrics;

    public Bitmap testSheet;
    public Bitmap flapdogBackground;
    public Bitmap flapdog_head;

    public SpriteSheet dogBarking, dogHappy, dogPlayfull, dogSad;

    public ResourceManager(Activity a){
        DisplayMetrics metrics = new DisplayMetrics();
        a.getWindowManager().getDefaultDisplay().getMetrics(metrics);
        this.metrics = metrics;
        screenWidth = metrics.widthPixels;
        screenHeight = metrics.heightPixels;
        dpscreenWidth = screenWidth/metrics.density;
        dpscreenHeight = screenHeight/metrics.density;
        R.drawable.dead_normal);

        initBitmaps(a.getResources());

        flapdogBackground = getResizedBitmap(BitmapFactory.decodeResource(a.getResources(), R.drawable.flapdog_bg), screenWidth, screenHeight);
        int flapdog_width = (int)getPercentageLength(10, true);
        flapdog_head = getResizedBitmap(BitmapFactory.decodeResource(a.getResources(), R.drawable.flapdog_head), flapdog_width, flapdog_width);
        log();
        INSTANCE = this;
    }

    private void initBitmaps(Resources r) {
        Bitmap b;
        double height = getPercentageLength(50, true);
        Log.e("heihgt", "Height is: "+height);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inPreferredConfig = Bitmap.Config.RGB_565;
        options.inDither = true;

        b = BitmapFactory.decodeResource(r, R.drawable.dog_happy_f30, options);
        dogHappy = new SpriteSheet(getResizedBitmap(b, (int)(b.getWidth()*(height*30)/b.getHeight()), (int) (height*30)), 30, false);
        b.recycle();
        b = BitmapFactory.decodeResource(r, R.drawable.dog_barking_f30, options);
        dogBarking = new SpriteSheet(getResizedBitmap(b, (int)(b.getWidth()*(height*30)/b.getHeight()), (int) (height*30)), 30, false);
        b.recycle();
        b = BitmapFactory.decodeResource(r, R.drawable.dog_playfull_f30, options);
        dogPlayfull = new SpriteSheet(getResizedBitmap(b, (int)(b.getWidth()*(height*30)/b.getHeight()), (int) (height*30)), 30, false);
        b.recycle();
        b = BitmapFactory.decodeResource(r, R.drawable.dog_sad_f30, options);
        dogSad = new SpriteSheet(getResizedBitmap(b, (int)(b.getWidth()*(height*30)/b.getHeight()), (int) (height*30)), 30, false);
    }

    public float convertDpToPixel(float dp){
        return dp * (metrics.densityDpi / 160f);
    }

    public float convertPixelsToDp(float px){
        return px / (metrics.densityDpi / 160f);
    }

    public double getPercentageLength(double percentage, boolean height){
        if(height){
            return (double)screenHeight*(percentage/100);
        }else{
            return (double)screenWidth*(percentage/100);
        }
    }

    private void log(){
        String s = "Recoursemanager initialized. \nscreenwidth: "+screenWidth + "\nscreenheight: "
                +screenHeight+"\ndpscreenwidth: "+dpscreenWidth+"\ndpscreenheight: "+dpscreenHeight;
        Log.d("RecourceManager", s);
    }

    public static Bitmap getResizedBitmap(Bitmap bm, int newWidth, int newHeight) {
        int width = bm.getWidth();
        int height = bm.getHeight();
        float scaleWidth = ((float) newWidth) / width;
        float scaleHeight = ((float) newHeight) / height;
        Matrix matrix = new Matrix();
        matrix.postScale(scaleWidth, scaleHeight);
        Bitmap resizedBitmap = Bitmap.createBitmap(bm, 0, 0, width, height, matrix, false);
        bm.recycle();
        return resizedBitmap;
    }

    public int getScreenWidth() {
        return screenWidth;
    }

    public int getScreenHeight(){
        return screenHeight;
    }
}

(这似乎是一种调整图像大小的愚蠢方法,因为 android 系统本身提供了从 mdpi hpdi 等文件夹加载适当大小的图像的功能。不知何故我不设法让它工作,这就是我尝试这种方法的原因。)

错误信息如下:

01-05 19:49:32.363 25711-25711/example.com.virtualpet I/art: Alloc partial concurrent mark sweep GC freed 18(704B) AllocSpace objects, 0(0B) LOS objects, 3% free, 421MB/437MB, paused 366us total 6.317ms
01-05 19:49:32.383 25711-25711/example.com.virtualpet I/art: Alloc concurrent mark sweep GC freed 13(12KB) AllocSpace objects, 0(0B) LOS objects, 3% free, 421MB/437MB, paused 396us total 18.615ms
01-05 19:49:32.383 25711-25711/example.com.virtualpet I/art: Forcing collection of SoftReferences for 140MB allocation
01-05 19:49:32.403 25711-25711/example.com.virtualpet I/art: Alloc concurrent mark sweep GC freed 11(344B) AllocSpace objects, 0(0B) LOS objects, 3% free, 421MB/437MB, paused 396us total 18.066ms
01-05 19:49:32.423 25711-25711/example.com.virtualpet E/art: Throwing OutOfMemoryError "Failed to allocate a 147456012 byte allocation with 16777120 free bytes and 90MB until OOM"
01-05 19:49:32.423 25711-25711/example.com.virtualpet D/AndroidRuntime: Shutting down VM
01-05 19:49:32.443 25711-25711/example.com.virtualpet E/AndroidRuntime: FATAL EXCEPTION: main
Process: example.com.virtualpet, PID: 25711
java.lang.OutOfMemoryError: Failed to allocate a 147456012 byte allocation with 16777120 free bytes and 90MB until OOM
at dalvik.system.VMRuntime.newNonMovableArray(Native Method)
at android.graphics.Bitmap.nativeCreate(Native Method)
at android.graphics.Bitmap.createBitmap(Bitmap.java:939)
at android.graphics.Bitmap.createBitmap(Bitmap.java:912)
at android.graphics.Bitmap.createBitmap(Bitmap.java:843)
at example.com.virtualpet.Util.ResourceManager.getResizedBitmap(ResourceManager.java:111)
at example.com.virtualpet.Util.ResourceManager.initBitmaps(ResourceManager.java:68)
at example.com.virtualpet.Util.ResourceManager.<init>(ResourceManager.java:41)
at example.com.virtualpet.MainActivity.onCreate(MainActivity.java:31)
at android.app.Activity.performCreate(Activity.java:6289)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1119)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2655)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2767)
at android.app.ActivityThread.access0(ActivityThread.java:177)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1449)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:145)
at android.app.ActivityThread.main(ActivityThread.java:5951)
at java.lang.reflect.Method.invoke(Native Method)
at java.lang.reflect.Method.invoke(Method.java:372)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1388)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1183)

Activity class:

public class MainActivity extends Activity {
    private DogView view;
    private boolean inGame = false;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new ResourceManager(this);
    }
}

我读过软链接,但也读到这不是解决此问题的正确方法。另外 bitmap.recycle() 似乎对我没有帮助,因为我需要所有的位图。

Ps。抱歉英语不好,我希望你能理解我的问题。

编辑:

忘了说我已经将 android:largeheap=true 添加到 android 清单文件中。

编辑2: 答案中提到并非同时需要所有图像。这是正确的,所以更好的问题是如何在实际使用图像之前高效快速地加载这些图像。

答案: 我开始在脑海中对您的评论进行更多思考,并提出了一个下降(满足我的需要)解决方案:不是同时加载所有图像,而是仅在我需要时加载。这种方法的缺点是图像不会立即改变,而是在不到一秒钟之后改变。谢谢你的时间。

首先在您的清单文件中将此添加到应用程序标签 android:largeheap=true 中,然后使用 universal image loaderpicasso 获取图像。别担心,这个库管理你的图像不会导致崩溃。 picasso and Universal Image Loader.

您应该尝试 inJustDecodeBounds。这将帮助您在不将图像加载到内存中的情况下获得图像的大小而无需缩放。

If set to true, the decoder will return null (no bitmap), but the out... fields will still be set, allowing the caller to query the bitmap without having to allocate the memory for its pixels.

狗叫是悲伤的、快乐的、顽皮的吗? 您需要尝试一种不同的方法来尝试做的事情。如果您甚至不使用它们,您绝对不需要内存中的每个精灵。

您是否考虑过使用 LibGDX?

它具有 SpriteSheets (TextureAtlas) 的实现以及用于构建精灵的工具。这样您就不必花时间编写自己的 spritesheet renderer/controller.

LibGDX Tutorial for TextureAtlas and Texture Packing

LibGDX 是一个构建游戏的框架,像你这样的虚拟宠物项目听起来很适合用它来实现。