4

I'm shooting for an animation in a live wallpaper. Here's the code. It pretty much follows the CubeWallpaper demo:

    void drawFrame() {
        final SurfaceHolder holder = getSurfaceHolder();
    final BufferedInputStream buf;
    final Bitmap bitmap, rbitmap;

        Canvas c = null;
        try {
            c = holder.lockCanvas();
            if (c != null) {
        try {
        buf = new 
            BufferedInputStream(assets.
                    open(folder+"/"
                         +imageList[ilen++])
                    );
        bitmap = BitmapFactory.
            decodeStream(buf);
        rbitmap = Bitmap.createBitmap
            (bitmap,
             0,0,imageWidth,imageHeight,
             transMatrix,false);
        c.drawBitmap(rbitmap,
                 paddingX,
                 paddingY,
                 null);
        if ( ilen >= imageCount ) ilen=0;
        }
        catch (Exception e) { e.printStackTrace(); }
            }
        } finally {
            if (c != null) holder.unlockCanvasAndPost(c);
        }

        // Reschedule the next redraw
        mHandler.removeCallbacks(mDrawCube);
        if (mVisible) {
            mHandler.postDelayed(mDrawCube, fps);
        }
    }

where "transMatrix" is a scaling and rotation matrix predefined before.

It's supposed to render at 30fps but of course it doesn't do that. My initial guess is that the BufferedInputStream is one factor. I should probably cache a few of these as I go along along with the Bitmaps. But any other ideas? Will I have to use the OpenGL hack for live wallpapers?

tshepang
  • 12,111
  • 21
  • 91
  • 136
U Avalos
  • 6,538
  • 7
  • 48
  • 81

2 Answers2

4

Yes, the BufferedInputStream and BitmapFactory really shouldn't be in drawFrame() at all. You're loading and creating resources on every single frame, and that's a huge waste. Like you said, cache as many as you can beforehand, and if you find the need to load more during drawing, use a separate thread to do it so it doesn't slow the drawing.

Geobits
  • 22,218
  • 6
  • 59
  • 103
  • 2
    Geobits is correct. What's more, the code as you have written it will induce a lot of garbage collection, slowing things down even further. – George Freeman May 29 '11 at 06:13
  • OK doing a seperate thread for caching and i'm still not getting a speed up. Here's an outline of my solution: 1. preload about 1 second of animations into an array of bitmaps, 2. draw each bitmap. 3. mark each cell as "empty" as I draw it. 4. a seperate thread "refilss" the empty cells with the next animations. – U Avalos May 31 '11 at 16:26
  • Sorry...this comment box doesn't like "enter" so I need to retype my comment: OK doing a seperate thread for caching and i'm still not getting a speed up. Here's an outline of my solution: 1. preload about 1 second of animations into an array of bitmaps, 2. draw each bitmap. 3. mark each cell as "empty" as I draw it. 4. a seperate thread "refills" the empty cells with the next animations. The problem is that the bitmap loading is always slow even with a thread, so you eventually get a bottleneck. I also tried loading a "block" of bitmaps at a time and same problem. Do I need to use opengl? – U Avalos May 31 '11 at 16:48
  • 1
    No, openGl won't speed up bitmpap loading -at all-, trust me. The fact is, loading, creating and destroying that many resources during the playback is never going to be fast. That's why most games with a lot of graphics have long load times at the beginning, so it doesn't slow down the gameplay. Exactly how many images(in terms of file size) are you trying to load? – Geobits May 31 '11 at 21:30
  • That's what I was worried about---bitmap loading is the problem. Probably around 150 bitmaps (5 second animation) which is way too many to preload at the original resolution. – U Avalos Jun 01 '11 at 17:10
3

I had the same problem: slow canvas rendering in context of live wallpapers.

I agree with others saying that you shouldn't do any cpu/io heavy while rendering e.g. loading images especially on the UI thread.

However there is one more thing you should note. You request a redraw (mHandler.postDelayed(...)) AFTER the frame was rendered. If you desire a 30 fps and thus you request a redraw in (1000 / 30) 33ms then it will NOT result in 30 frames per sec. Why?

Let's assume it takes 28ms to render all your stuff to the canvas. After it's done you request a redraw after 33 millis. That is, the period between redraws is 61 ms which equals with 16 frames per sec.

You have two options to solve it:

1) Put the mHandler.postDelayed(...) code to the beginning of the drawFrame(...) method. This seems OK but it has some disadvantages: If your actual FPS is very close to the maximal possible FPS on an actual device - with other words the UI thread is busy all the time with you canvas rendering - then there won't be time for the UI thread to do other stuff. It doesn't necesseraly mean that your LWP or the home screen will lag but you (your LWP) might start missing some touch events (as my LWP did).

2) The better solution is to start a separate thread when the surface is created and pass it the reference to the SurfaceHolder. Do the rendering in this separate thread. The render method in this thread would look like this:

private static final int DESIRED_FPS = 25;
private static final int DESIRED_PERIOD_BETWEEN_FRAMES_MS = (int) (1000.0 / DESIRED_FPS + 0.5);



@Override
public void run() {
    while (mRunning) {
        long beforeRenderMs = SystemClock.currentThreadTimeMillis();            
        // do actual canvas rendering
        long afterRenderMs = SystemClock.currentThreadTimeMillis();
        long renderLengthMs = afterRenderMs - beforeRenderMs;
        sleep(Math.max(DESIRED_PERIOD_BETWEEN_FRAMES_MS - renderLengthMs, 0));
    }
}
Zsolt Safrany
  • 13,290
  • 6
  • 50
  • 62