views:

115

answers:

4

I've been trying to make a simple live wallpaper out of a boot animation. So basically i have about 50 .pngs in my drawable folder. I'm able to set the animation to about 10-20 frames and it works great. But once i set it to about 30 frames...I get an OutOfMemory Error. I was hoping maybe someone could take a look at my code and maybe give an example of how I could achieve more frames? That would help so much i've been looking at this for hours > <

Here's my code:

package com.androidnetwork.animlivewp;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Paint;
import android.os.Handler;
import android.os.SystemClock;
import android.service.wallpaper.WallpaperService;
import android.util.Log;
import android.view.MotionEvent;
import android.view.SurfaceHolder;

public class AnimatedLiveWallpaper extends WallpaperService {

    private final Handler mHandler = new Handler();

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
    }

    @Override
    public Engine onCreateEngine() {
        return new CubeEngine();
    }

    class CubeEngine extends Engine {

        private final Paint mPaint = new Paint();
        private float mPosY;
        private boolean mAnime = true;
        private Matrix mMatrix = new Matrix();

        private final Runnable mDrawAnim = new Runnable() {
            public void run() {
                drawFrame();
            }
        };
        private boolean mVisible;

        private static final int NUM_RES = 30;
        private final Bitmap[] mPics = new Bitmap[NUM_RES];
        CubeEngine() {
            Resources res = getResources();
            for (int i = 0; i< NUM_RES; i++) {
                int id = res.getIdentifier("boot_00" + (100 + (i + 1)), "drawable", "com.androidnetwork.animlivewp");
                mPics[i] = BitmapFactory.decodeResource(res, id);
            }
        }

        @Override
        public void onCreate(SurfaceHolder surfaceHolder) {
            super.onCreate(surfaceHolder);

            setTouchEventsEnabled(false);
        }

        @Override
        public void onDestroy() {
            super.onDestroy();
            mHandler.removeCallbacks(mDrawAnim);
        }

        @Override
        public void onVisibilityChanged(boolean visible) {
            mVisible = visible;
            if (visible) {
                drawFrame();
            } else {
                mHandler.removeCallbacks(mDrawAnim);
            }
        }

        @Override
        public void onSurfaceChanged(SurfaceHolder holder, int format, int width, int height) {
            super.onSurfaceChanged(holder, format, width, height);

            float w = mPics[0].getWidth();
            float h = mPics[0].getHeight();
            float s = width / (float)w;
            mMatrix.reset();
            mMatrix.setScale(s, s);

            mPosY = (height - (h * s)) / 2f;
            drawFrame();
        }

        @Override
        public void onSurfaceCreated(SurfaceHolder holder) {
            super.onSurfaceCreated(holder);
        }

        @Override
        public void onSurfaceDestroyed(SurfaceHolder holder) {
            super.onSurfaceDestroyed(holder);
            mVisible = false;
            mHandler.removeCallbacks(mDrawAnim);
        }

        @Override
        public void onOffsetsChanged(float xOffset, float yOffset,
                float xStep, float yStep, int xPixels, int yPixels) {
            drawFrame();
        }


        @Override
        public void onTouchEvent(MotionEvent event) {
            if (event.getAction() == MotionEvent.ACTION_MOVE) {
                mAnime = !mAnime;
            }
            super.onTouchEvent(event);
        }


        void drawFrame() {
            final SurfaceHolder holder = getSurfaceHolder();

            Canvas c = null;
            try {
                c = holder.lockCanvas();
                if (c != null) {
                    // draw something
                    drawAnim(c);
                    //drawTouchPoint(c);
                }
            } finally {
                if (c != null) holder.unlockCanvasAndPost(c);
            }

            // Reschedule the next redraw
            mHandler.removeCallbacks(mDrawAnim);
            if (mVisible && mAnime) {
                mHandler.postDelayed(mDrawAnim, 1000 / 10);
            }
        }


        private int idx = 0;
        void drawAnim(Canvas c) {
            c.save();
            c.translate(0, mPosY);
            c.drawBitmap(mPics[idx], mMatrix, mPaint);
            if (mAnime) ++idx;
            if (idx == NUM_RES) idx = 0;

            c.restore();
        }



    }
}

And here's a logcat if that'll help at all:

08-22 19:45:05.508: ERROR/AndroidRuntime(12277): FATAL EXCEPTION: main
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277): java.lang.OutOfMemoryError: bitmap size exceeds VM budget
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.Bitmap.nativeCreate(Native Method)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.Bitmap.createBitmap(Bitmap.java:468)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.Bitmap.createBitmap(Bitmap.java:435)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.Bitmap.createScaledBitmap(Bitmap.java:340)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.BitmapFactory.finishDecode(BitmapFactory.java:488)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.BitmapFactory.decodeStream(BitmapFactory.java:462)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.BitmapFactory.decodeResourceStream(BitmapFactory.java:323)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:346)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.graphics.BitmapFactory.decodeResource(BitmapFactory.java:372)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at com.androidnetwork.animlivewp.AnimatedLiveWallpaper$CubeEngine.<init>(AnimatedLiveWallpaper.java:55)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at com.androidnetwork.animlivewp.AnimatedLiveWallpaper.onCreateEngine(AnimatedLiveWallpaper.java:32)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.service.wallpaper.WallpaperService$IWallpaperEngineWrapper.executeMessage(WallpaperService.java:814)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at com.android.internal.os.HandlerCaller$MyHandler.handleMessage(HandlerCaller.java:61)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.os.Handler.dispatchMessage(Handler.java:99)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.os.Looper.loop(Looper.java:123)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at android.app.ActivityThread.main(ActivityThread.java:4627)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at java.lang.reflect.Method.invokeNative(Native Method)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at java.lang.reflect.Method.invoke(Method.java:521)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
    08-22 19:45:05.508: ERROR/AndroidRuntime(12277):     at dalvik.system.NativeStart.main(Native Method)
A: 

android.graphics.Bitmap.createScaledBitmap(Bitmap.java:340) means, that Android generates a new Bitmap because you didn't use the drawable-*dpi folders correctly... that can double your needed memory usage

WarrenFaith
A: 

Back at my last company, where I was working on a mobile platform, we were having some fairly serious performance issues. We did a fair amount of investigation, but we generally seeing that just everything was running slow and were having trouble determining why. Eventually we went to the chipset vendor for help on why things were running slow. After some time, they came back with the answer: "you are executing too much code."

There is a certain truth to that.

And a similar answer is probably appropriate here: you are using too much memory.

You just need to reduce the number or size of bitmaps you are using.

hackbod
A: 

You can't load so many bitmap into memory.

You can load limit number bitmap into memory, and when It is need to show other picture.

You can free some memory with bitmap's recycle() method, and create new bitmap. If you wait the GC to garbage collection, the memory is already not enough.

The key point is don't load too many bitmap and recycle() when not show in view.

imcaptor
do you know of any examples i could check out that show the best way to make an animation with like 50+ picture files?
brybam
A: 

Only create one bitmap and reload each png each time you draw your canvas. For example create a simple routine that will reload each image into the same bitmap allocation. I would also suggest that you convert the png files into jpg files, as png are a lossless format. With jpg you can compress each frame slightly.

public void updateBG() {

idx += 1;
if (idx == NUM_RES) {idx = 0;}
switch (bgcycle) {
    case 0: myBg = BitmapFactory.decodeResource(getResources(),R.drawable.frame1); break;
    case 1: myBg = BitmapFactory.decodeResource(getResources(),R.drawable.frame2); break;
    case 2: myBg = BitmapFactory.decodeResource(getResources(),R.drawable.frame3); break;
            case etc....
        }}

Or I guess you could use this if you want to link to the bootanimation

int id = res.getIdentifier("boot_00" + (100 + (idx + 1)), "drawable", "com.androidnetwork.animlivewp");
            myBg = BitmapFactory.decodeResource(res, id);

Then in your DrawAnim code just

updateBG();
c.drawBitmap(myBg, mMatrix, null);
Dean