Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
154 views
in Technique[技术] by (71.8m points)

Android Java out of memory exception loading spritsheets

I want do make a virtual pet on a android device, but I got problems loading all the spritesheets (these are .png files). I can load a certain amount of sheets withouth crashing, but not all. Of course this is due to the lack of enough memory. The images are each less then 650 kB (each image has a resolution of roughly 200x6300) , so I guess i'm doing something horrible wrong. My question: "How to load all these images withouth crashing due to an Out of Memory Exception?"

I load the images in a singleton class. This class is responsible for all the resources the application needs. Here is the code I use to load the images.

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. 
screenwidth: "+screenWidth + "
screenheight: "
                +screenHeight+"
dpscreenwidth: "+dpscreenWidth+"
dpscreenheight: "+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;
    }
}

(this may seem a bit of a stupid way to resize the images, because the android system itself offers the functionallity to load proper sized images from the mdpi hpdi etc. folders. Somehow I don't manage to get that working, that's why I'm trying this approach.)

Here is the error message:

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.access$900(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)

The 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);
    }
}

I've read about softlinks, but also read that isn't a proper solution to this problem. Also the bitmap.recycle() doesn't seem to help In my case, because I need all the bitmaps.

Ps. Sorry for the bad english, I hope you manage to understand my problem.

EDIT:

Forgot to mention I already added android:largeheap=true to the android manifest file.

EDIT2: Was mentioned in the answer that not all the images are needed at the same moment. This is correct, so a better question would be how to load these images efficiently and fast just moments before actually using the image.

ANSWER: I started to do some more thinking with the comments of you in my mind and came with a descent (for my needs) solution: not loading all the images at the same time, but only when I need it. The drawback of this approach is that the images doesn't change immediatly, but after a less then a second. Thank you for your time.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

first of all in your manifest file add this inside application tag android:largeheap=true then use universal image loader or picasso for getting images. do not worry, this libraries manage your images to not be led to crash. picasso and Universal Image Loader.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...