A good User Interface (UI) has both a high frame rate and a smooth frame rate, meaning there is no visible jitter in the speed at which the screen updates. We can animate using our own Thread or a Timer, and we can perform the animation logic changes just before we paint on a different thread. Which approach is best at updating the screen quickly and not making logic changes appear to “lag” due to the painter falling behind?
There are several related performance questions to answer here:
Is Timer.scheduleAtFixedRate(40) or a Thread.sleep(40) faster at animating my GameCanvas?
Is Timer.scheduleAtFixedRate(40) or a Thread.sleep(40) more visibly smooth at animating my GameCanvas?
How many Threads (or TimerTasks on a single Timer) should I use, including the renderer?
Should I update the screen model on the same Thread/TimerTask as the renderer, or a different one?
Let’s first look at the raw test results where one Renderer and 0-3 additional Threads or TimerTasks did some repetitive math (int a = 0; a += 34, repeat 1000 times) to create processor load.
# threads/ timertasks |
Wake Up Interval (ms) |
scheduleAtFixedRate() |
logic (moveX) in render or “worker” thread/timertask |
FPS with threads |
FPS with timertasks |
Smoothness with threads (related to fps) |
Smoothness with timertasks (related to fps) |
---|---|---|---|---|---|---|---|
1 |
40 |
yes |
render |
12 |
23 |
quite smooth |
really smooth |
2 |
40 |
yes |
render |
16 |
22 |
quite smooth |
quite smooth |
4 |
40 |
yes |
render |
19 |
22 |
smooth |
smooth |
1 |
40 |
yes |
worker |
12 |
23 |
not very smooth |
really smooth |
2 |
40 |
yes |
worker |
16 |
22 |
not very smooth |
not smooth |
4 |
40 |
yes |
worker |
19 |
22 |
quite smooth |
not very smooth |
4 |
1 |
yes |
render |
19 |
23 |
laggy |
laggy |
4 |
1 |
no |
render |
- |
23 |
- |
smooth |
From these systematic tests, we can see results that may seem surprising:
With Threads, the smoothness and rendering frame rate actually increased with more worker Threads, so multiple threads was not a problem in these tests and actually helped visible animation by apparently decreasing the time slice per Thread when there were more Threads. This is unexpected, but worth keeping in mind: allocating more Threads may actually make your multitasking UI appear more smooth.
TimerTask.scheduleAtFixedRate(40) adapts well and gives a higher frame rate than a fixed Thread.sleep(40). This is because the Thread version is not dynamically adapting to the load from other Threads. Thus, to achieve full 25 FPS, the simple Thread code used should be improved to sleep not for a fixed 40 ms, but a value less than 40 ms calculated using System.currentTimeMillis().
With Thread and TimerTask, setting a very high repetition rate made the UI feel laggy as the Timer tried to keep up with a rate it could not sustain. Thus setting a fixed rate with Timer works well, but only when you tune to a lower rate than the maximum, the phone can sustain. In both cases, you can remove the sensation of display lagging behind the input if you make your renderer adapt to the current frame rate rather than blindly assume the requested frame rate will be achieved.
With Thread and Timer, it is a bad idea to put separate TimerTasks or Threads with one doing the model updates and another doing the rendering. In both cases, model updates and similar loads should be done just before rendering.