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 Thread
s (or TimerTask
s 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 Thread
s or TimerTask
s 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 Thread
s, the smoothness and rendering frame rate actually
increased with more worker Thread
s, 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 Thread
s. This is unexpected,
but worth keeping in mind: allocating more Thread
s 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 Thread
s. 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 TimerTask
s or Thread
s 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.