MREZA MREZA - 4 months ago 11
Android Question

Do a task exactly in precise time no matter what in java/android

I know there are a lot of problems but perhaps by freezing another task to achieve the nearest result possible or by using a parallel thread?

Here is my code:

public void draw(Canvas canvas) {
DrawButtons(canvas);
DrawPercise(canvas);
DrawLines(canvas);
}

private void DrawButtons(Canvas canvas) {
canvas.drawBitmap(Button, 50, 0, null);
}

private void DrawPercise(Canvas canvas) {

if (System.nanoTime() >= AllowedTimeinNano) {
// Save time again for Next if
//if 50000000 nanoseconds passed do it again
AllowedTimeinNano = (long) (System.nanoTime() + (20000000000f / 400));

DoTask();

}
}

private void DrawLines(Canvas canvas) {

for (float i = 40; i < 800; i += 40) {
canvas.drawLine(0, i, 800, i, TablePaint);
}

}


The problem is if my task takes too long or the target device has bad performance, then the timing becomes incorrect, and the whole point of the app is based on this timing. I know this may be impossible but could you give some tips?

Answer

I thought I would answer this question to clairify what I said in the comment.

Thread:

public class GameThread extends Thread  {

    private int FPS = 60;
    private double averageFPS;
    private SurfaceHolder surfaceHolder;
    private Clicker gamePanel;
    private boolean running;
    public static Canvas canvas;

    public GameThread(SurfaceHolder surfaceHolder, Clicker gamePanel)
    {
        super();
        this.surfaceHolder = surfaceHolder;
        this.gamePanel = gamePanel;
    }

    @Override
    public void run()
    {
        long startTime;
        long timeMillis;
        long waitTime;
        long totalTime = 0;
        int frameCount =0;
        long targetTime = 1000/FPS;




        while(running) {
            startTime = System.nanoTime();
            canvas = null;

            //try locking the canvas for pixel editing
            try {
                canvas = this.surfaceHolder.lockCanvas();
                synchronized (surfaceHolder) {
                    this.gamePanel.tick();
                    this.gamePanel.draw(canvas);
                }
            } catch (Exception e) {
            }
            finally{
                if(canvas!=null)
                {
                    try {
                        surfaceHolder.unlockCanvasAndPost(canvas);
                    }
                    catch(Exception e){e.printStackTrace();}
                }
            }




            timeMillis = (System.nanoTime() - startTime) / 1000000;
            waitTime = targetTime-timeMillis;

            try{
                sleep(waitTime);
            }catch(Exception e){}

            totalTime += System.nanoTime()-startTime;
            frameCount++;
            if(frameCount == FPS)
            {
                averageFPS = 1000/((totalTime/frameCount)/1000000);
                frameCount =0;
                totalTime = 0;
                System.out.println(averageFPS);
            }
        }
    }
    public void setRunning(boolean b)
    {
        running=b;
    }

}

I got it when I took a tutorial when I started up, and I have only used this. Before you can use it there are some things you have to do:

  • Have a class that extends SurfaceView implements SurfaceHolder.Callback
  • Replace 'Clicker' with the name of your class.

For FPS calculation:

milliseconds in 1 second / FPS = how often in milliseconds it will update

1000 / 60 = 16.666666

Which means it updates every 0.01666666 seconds

How do I use it?

Simple. (You have to find the propper places to place them yourself)

Creating it:

if(gt == null) {
    gt = new GameThread(getHolder(), this);
    gt.setRunning(true);
    gt.start();
}

Stopping it:

if(gt != null) {
        try {
            gt.setRunning(false);
            gt.join();
            gt = null;

        } catch (InterruptedException e) {
            Toast.makeText(c, "An error occured when stopping the thread.", Toast.LENGTH_LONG)
                    .show();
        }
    }

In my experience, 60 FPS is the best FPS to take to secure that this will work on all devices. All though, there are some exceptions.

Normal phones today have 2GB of ram or more. For an instance, S7 has 4 gigabytes of ram. However, if there is 512 MB of ram, I'm not sure as of performance. But there are very few devices left in the world with 512 MB RAM. There are some budget phones, but there are not a lot of those with only 512 MB of RAM unless you would select the really old versions.

So, by using the thread above, you will have a game thread that updates contantly and will never stop. You do not have to worry performance wise, because there are mostly new devices running for real potential customers.

Additionally, this thread works in a very simple way:

FPS is the max FPS and will therefore not go higher, so lower end devices will go for as high as possible up to 60, while newer will stay steady at 60.

All though I have experienced with my phone that it sometimes go up to 62, but it isn't really a problem because it is only two FPS too much.

Remember:

The more actions that happen in methods touched by the gamethread, the more strain it is on the system and on the app. Any game can reach 2000 fps if there is no limit and nothing happens. While loops are fast!