Galaxy Nexus slow with animations
Asked Answered
F

4

7

I've edited this code to move the Rect instantiations out of the onDraw method. I've tested it on several devices.

public class BallBouncesActivity extends Activity {
    BallBounces ball;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ball = new BallBounces(this);
        setContentView(ball);
    }
}


class BallBounces extends SurfaceView implements SurfaceHolder.Callback {
    GameThread thread;
    int screenW; //Device's screen width.
    int screenH; //Devices's screen height.
    int ballX; //Ball x position.
    int ballY; //Ball y position.
    int initialY ;
    float dY; //Ball vertical speed.
    int ballW;
    int ballH;
    int bgrW;
    int bgrH;
    int angle;
    int bgrScroll;
    int dBgrY; //Background scroll speed.
    float acc;
    Bitmap ball, bgr, bgrReverse;
    boolean reverseBackroundFirst;
    boolean ballFingerMove;

    Rect toRect1, fromRect1;
    Rect toRect2, fromRect2;

    //Measure frames per second.
    long now;
    int framesCount=0;
    int framesCountAvg=0;
    long framesTimer=0;
    Paint fpsPaint=new Paint();

    //Frame speed
    long timeNow;
    long timePrev = 0;
    long timePrevFrame = 0;
    long timeDelta;


    public BallBounces(Context context) {
        super(context);
        ball = BitmapFactory.decodeResource(getResources(), R.drawable.rocket); //Load a ball image.
        bgr = BitmapFactory.decodeResource(getResources(), R.drawable.sky_bgr); //Load a background.
        ballW = ball.getWidth();
        ballH = ball.getHeight();

        toRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
        fromRect1 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
        toRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());
        fromRect2 = new Rect(0, 0, bgr.getWidth(), bgr.getHeight());

        //Create a flag for the onDraw method to alternate background with its mirror image.
        reverseBackroundFirst = false;

        //Initialise animation variables.
        acc = 0.2f; //Acceleration
        dY = 0; //vertical speed
        initialY = 100; //Initial vertical position
        angle = 0; //Start value for the rotation angle
        bgrScroll = 0;  //Background scroll position
        dBgrY = 1; //Scrolling background speed

        fpsPaint.setTextSize(30);

        //Set thread
        getHolder().addCallback(this);

        setFocusable(true);
    }

    @Override
    public void onSizeChanged (int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        //This event-method provides the real dimensions of this custom view.
        screenW = w;
        screenH = h;

        bgr = Bitmap.createScaledBitmap(bgr, w, h, true); //Scale background to fit the screen.
        bgrW = bgr.getWidth();
        bgrH = bgr.getHeight();

        //Create a mirror image of the background (horizontal flip) - for a more circular background.
        Matrix matrix = new Matrix();  //Like a frame or mould for an image.
        matrix.setScale(-1, 1); //Horizontal mirror effect.
        bgrReverse = Bitmap.createBitmap(bgr, 0, 0, bgrW, bgrH, matrix, true); //Create a new mirrored bitmap by applying the matrix.

        ballX = (int) (screenW /2) - (ballW / 2) ; //Centre ball X into the centre of the screen.
        ballY = -50; //Centre ball height above the screen.
    }

    //***************************************
    //*************  TOUCH  *****************
    //***************************************
    @Override
    public synchronized boolean onTouchEvent(MotionEvent ev) {

        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN: {
                ballX = (int) ev.getX() - ballW/2;
                ballY = (int) ev.getY() - ballH/2;

                ballFingerMove = true;
                break;
            }

            case MotionEvent.ACTION_MOVE: {
                ballX = (int) ev.getX() - ballW/2;
                ballY = (int) ev.getY() - ballH/2;

                break;
            }

            case MotionEvent.ACTION_UP:
                ballFingerMove = false;
                dY = 0;
                break;
            }
        return true;
    }

    @Override
    public void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        //Draw scrolling background.
        fromRect1.set(0, 0, bgrW - bgrScroll, bgrH);
        toRect1.set(bgrScroll, 0, bgrW, bgrH);

        fromRect2.set(bgrW - bgrScroll, 0, bgrW, bgrH);
        toRect2.set(0, 0, bgrScroll, bgrH);

//        Rect fromRect1 = new Rect(0, 0, bgrW - bgrScroll, bgrH);
//        Rect toRect1 = new Rect(bgrScroll, 0, bgrW, bgrH);
//
//        Rect fromRect2 = new Rect(bgrW - bgrScroll, 0, bgrW, bgrH);
//        Rect toRect2 = new Rect(0, 0, bgrScroll, bgrH);

        if (!reverseBackroundFirst) {
            canvas.drawBitmap(bgr, fromRect1, toRect1, null);
            canvas.drawBitmap(bgrReverse, fromRect2, toRect2, null);
        }
        else{
            canvas.drawBitmap(bgr, fromRect2, toRect2, null);
            canvas.drawBitmap(bgrReverse, fromRect1, toRect1, null);
        }

        //Next value for the background's position.
        if ( (bgrScroll += dBgrY) >= bgrW) {
            bgrScroll = 0;
            reverseBackroundFirst = !reverseBackroundFirst;
        }

        //Compute roughly the ball's speed and location.
        if (!ballFingerMove) {
            ballY += (int) dY; //Increase or decrease vertical position.
            if (ballY > (screenH - ballH)) {
                dY=(-1)*dY; //Reverse speed when bottom hit.
            }
            dY+= acc; //Increase or decrease speed.
        }

        //Increase rotating angle
        if (angle++ >360)
            angle =0;

        //DRAW BALL
        //Rotate method one
        /*
        Matrix matrix = new Matrix();
        matrix.postRotate(angle, (ballW / 2), (ballH / 2)); //Rotate it.
        matrix.postTranslate(ballX, ballY); //Move it into x, y position.
        canvas.drawBitmap(ball, matrix, null); //Draw the ball with applied matrix.

        */// Rotate method two

        canvas.save(); //Save the position of the canvas matrix.
        canvas.rotate(angle, ballX + (ballW / 2), ballY + (ballH / 2)); //Rotate the canvas matrix.
        canvas.drawBitmap(ball, ballX, ballY, null); //Draw the ball by applying the canvas rotated matrix.
        canvas.restore(); //Rotate the canvas matrix back to its saved position - only the ball bitmap was rotated not all canvas.

        //*/

        //Measure frame rate (unit: frames per second).
         now=System.currentTimeMillis();
         canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
         framesCount++;
         if(now-framesTimer>1000) {
                 framesTimer=now;
                 framesCountAvg=framesCount;
                 framesCount=0;
         }
    }

    @Override
    public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
    }

    @Override
    public void surfaceCreated(SurfaceHolder holder) {
        thread = new GameThread(getHolder(), this);
        thread.setRunning(true);
        thread.start();
    }

    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        boolean retry = true;
        thread.setRunning(false);
        while (retry) {
            try {
                thread.join();
                retry = false;
            } catch (InterruptedException e) {

            }
        }
    }


    class GameThread extends Thread {
        private SurfaceHolder surfaceHolder;
        private BallBounces gameView;
        private boolean run = false;

        public GameThread(SurfaceHolder surfaceHolder, BallBounces gameView) {
            this.surfaceHolder = surfaceHolder;
            this.gameView = gameView;
        }

        public void setRunning(boolean run) {
            this.run = run;
        }

        public SurfaceHolder getSurfaceHolder() {
            return surfaceHolder;
        }

        @Override
        public void run() {
            Canvas c;
            while (run) {
                c = null;

                //limit frame rate to max 60fps
                timeNow = System.currentTimeMillis();
                timeDelta = timeNow - timePrevFrame;
                if ( timeDelta < 16) {
                    try {
                        Thread.sleep(16 - timeDelta);
                    }
                    catch(InterruptedException e) {

                    }
                }
                timePrevFrame = System.currentTimeMillis();

                try {
                    c = surfaceHolder.lockCanvas(null);
                    synchronized (surfaceHolder) {
                       //call methods to draw and process next fame
                        gameView.onDraw(c);
                    }
                } finally {
                    if (c != null) {
                        surfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }
    }
}

If you notice, there's code to measure the frame rate:

     now=System.currentTimeMillis();
     canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint);
     framesCount++;
     if(now-framesTimer>1000) {
             framesTimer=now;
             framesCountAvg=framesCount;
             framesCount=0;
     }

I'm seeing that on both of my Galaxy Nexus devices, running Android 4.0 and 4.2, it's around 22-24fps. On my HTC Desire, running Android 2.2, it's more like 60fps.

You'll also notice that I'm not allocating anything in the onDraw method. I'm not creating new Paint objects either. I really don't see how this is running so, so, so much slower on the my Galaxy Nexus devices. There's a lot of stuttering and the ball moves very slowly.

Does anyone know whether there is a setting I can undo or a known issue with re-drawing on the Galaxy Nexus? This is happening to my Galaxy Nexus that's running 4.0 and the one that's running 4.2, so I'm not sure it's OS-specific. I have turned off the window and transition animations in Developer Options. It doesn't make a difference whether I force 2D acceleration.

Farthingale answered 31/1, 2013 at 1:42 Comment(10)
I had exactly the same problem, my animations worked fine on older Android phones (Froyo / Gingerbread) but on my new Google Nexus 10, it just looked awful. In the end, I found out it was because I was re-colouring my sprites as I was drawing them, although I'm not sure why the issue didn't present itself on the lower powered devices! I took out the re-painting and it runs perfectly now. Must be an oddity with 4.x although if it's the Nexus 10 it could be the massive screen resolution. Is it possible for you to somehow create your fromRect and toRect outside of your draw loop?Geoponics
That's the entire program. So, I'm not sure where I could create the fromRect and toRects outside of the draw loop. Do you have any suggestions?Farthingale
What I've done is created an 'Initialise()' method in my main class (so in your 'Ballbounces' class) and initialise/create everything in here. Then call it from onCreate(). I guess you could create it in your constructor too?! Thing is, you're creating objects in your onDraw which will hit performance, From Android: "Creating drawing objects within your onDraw() method significantly reduces performance and can make your UI appear sluggish."Geoponics
So, I guess what I would do, is create the rectangle ahead with Rect fromRect1 = new Rect(); and then just set it's parameters within the draw method with something like fromRect1.set(left, top, right, bottom), this way, it's only being created once and it may help with performance. Hope this helpsGeoponics
@Geoponics Thanks for the suggestions! I actually tried to move the Rect instantiations to onCreate and setting the parameters in the draw method, but I'm still seeing the lag! sigh It's so frustrating that this is happening only on this device. :(Farthingale
Updated here: pastebin.com/yFm5Wan0, and I edited the question :)Farthingale
@Geoponics It is not what I would call on oddity : with its massive resolution, the nexus 10 is simply less forgiving with coding errors.Dicks
@DarrenGreen tell me about it - luckily I managed to get round my problem, everything is working quite nice on my N10 now and I have quite a few sprites moving around! (about 50 or so) - which Nexus device do you have? There doesn't look to be anything too taxing in your code that could cause the lag that I can see apart from the line canvas.drawText(framesCountAvg+" fps", 40, 70, fpsPaint); - again, this was exactly the problem I was having, drawing a bitmap with a paintObject but then I was drawing 24 of them. I changed paint to null and it worked like a dream. Was that code there before?Geoponics
As you're only drawing the one and it's text rather than a bitmap I can't see that being an issue. I know your limiting to 60FPS but have you removed you thread sleeping code to see if it makes any difference? I generally would suggest commenting out bit's of code at a time to see if you can isolate any piece that may be causing a problem. @Dicks You're right of course, I just couldn't rule out 4.x as the N10 is my only JellyBean device and my code worked fine on my older devices 2.x devices.Geoponics
@Geoponics I'm using a Galaxy Nexus: forum.xda-developers.com/forumdisplay.php?f=1336. I will try increasing the frame rate, but I still find it odd that the frame rate is so shitty on this single model with such a simple program. I have many devices ranging from an HTC Desire to a Nexus 4. None of them have trouble keeping the fps up at 60. It's so strange!Farthingale
G
0

I've been thinking about this, as I'm still having performance issues on my Nexus 10 with surfaceview, like I said previously, I increased my performance a lot my removing the use of paint objects, it's a long shot as you are only using one, but you could try to remove the text drawing section from your onDraw() just to see if it makes any difference to your drawing speed.

Other than that, I think it's really a case of trying to pin down the problem.

I've noticed that even if I remove my onDraw and Logic updating methods completely from the equation, it's still taking sometimes up to 25ms just to lock and unlock/post the canvas! So it might be that the problem actually doesn't lie in onDraw method - give it a go, comment onDraw() out from your Run() method and see what speeds you get (Use logging to Logcat to see the figures, remember that ironically, displaying a frame / time count on the screen could affect the very thing you're measuring). :-)

Geoponics answered 17/2, 2013 at 21:19 Comment(0)
P
1

Have you tested android:hardwareAccelerated="true|false" flag in application?

Android Dev Doc: http://developer.android.com/guide/topics/graphics/hardware-accel.html

You can add before setContentView:

if (android.os.Build.VERSION.SDK_INT >= 11) {
   getWindow().setFlags(WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED, WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED);
}

Pella answered 6/2, 2013 at 15:37 Comment(0)
V
0

What's your targetSdk set to?

If targetSdk is set to 8 (2.2), but you're running it on a 4.x device (15+), the 4.x device will run in compatibility mode, meaning that it will virtualize all the calls so that they return exactly the same as if they're running on a version 8 device.

This virtualization might explain some of the slowness. Try changing targetSdk to 17 (for your 4.2 device) and see if it makes a difference.

Volvox answered 13/2, 2013 at 18:53 Comment(2)
I'm using: <uses-sdk android:targetSdkVersion="17" android:minSdkVersion="14"/>Farthingale
It's still slow (sorry, forgot to mention that). I think it's interesting that most of the time, it's running at ~30fps. Once in a while, for a couple of seconds, it'll run at ~60fps. It's so weird!Farthingale
G
0

I've been thinking about this, as I'm still having performance issues on my Nexus 10 with surfaceview, like I said previously, I increased my performance a lot my removing the use of paint objects, it's a long shot as you are only using one, but you could try to remove the text drawing section from your onDraw() just to see if it makes any difference to your drawing speed.

Other than that, I think it's really a case of trying to pin down the problem.

I've noticed that even if I remove my onDraw and Logic updating methods completely from the equation, it's still taking sometimes up to 25ms just to lock and unlock/post the canvas! So it might be that the problem actually doesn't lie in onDraw method - give it a go, comment onDraw() out from your Run() method and see what speeds you get (Use logging to Logcat to see the figures, remember that ironically, displaying a frame / time count on the screen could affect the very thing you're measuring). :-)

Geoponics answered 17/2, 2013 at 21:19 Comment(0)
C
0

I has experienced the same thing on Kindle Fire, Sony Xperia Z and Samsung S4 (all with android 4.2).

The fix is: at App Manifest file remove "android:supportsRtl="true"".

Hope it will save your time. I spend 4 hours on tests and merging before i got it.

Clamber answered 11/2, 2014 at 13:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.