How to Reduce Thread.sleep() Latency in Android

2019-02-24 05:29发布

问题:

I am generating timing events in a loop in a (non-UI) thread in an Android application, and I need those events to occur at precise intervals in time, (precise here means not varying any more than +/- 5 millisecs). Any errors of +/-10 millisecs (and certainly +/- 20 millisecs) can be perceived by the user. At the top of this loop I do some other calculations that take a variable amount of time, but at the bottom of the loop, I need the event to occur at a pre-calculated time.

A higly simplified version (without exception handling) of one attempt at my non-UI thread is below:

public final void run() {

    long loopTime = 2500L;
    long eventTime = (System.nanoTime() / 100000L) + loopTime;

    while (true) {

        calcutionsTakingVaryingAmountOfTime(); // takes 200 millisecs or less

        long eventWait = eventTime - (System.nanoTime() / 100000L);
        Thread.sleep(eventWait / 10L);
        listener.onEvent();

        eventTime = eventTime + loopTime;
    }
}

It is the call to listener.onEvent() that needs to be precisely timed.

In the example above, the timing variables loopTime, eventTime, and eventWait measure time in tenths of a millisec. The expressions (System.nanoTime() / 100000L) measuring current time are likewise in tenths of a millisec.

I am absolutely certain that calcutionsTakingVaryingAmountOfTime() always takes less than 200 millisecs, and the call to listener.onEvent() is just a few millisecs. So as it stands, with loopTime set to 2500L, my events ought to occur every 250 millisecs.

I have intstrumented my code (not shown) to print to Log.d() the latency in the Thread.sleep() wake up time. That is, I calculate

long latency = (System.nanoTime() / 100000L) - eventTime

immediately after the return from Thread.sleep(), and print it to Log.d().

When I run this in the emulator, what I find is that latency (after dividing by 10 to get the result into millisecs) is normally jumping between 1 and 50 millisecs in successive passes through the loop, with occasional values as high as a half a second. When run on an actual device, things are quite a bit better, but still a little wobbly (and even still, the emulator behavior makes me wonder if this is going to happen on users' devices).

To try to steady my event and control the latency, I tried several other approaches:

  • In replaced the Thread.sleep(eventWait / 10L) call, with a call to this.wait(eventWait / 10L) (a completely inappropriate use of wait(), I know)

  • I manipulated the thread priorities prior to entering the loop, calling Process.setThreadPriority(Process.THREAD_PRIORITY_URGENT_AUDIO) like is done all over the android libraries

But there was no improvement at all in the latency with these.

The one approach that steadys the event, and reduces the latency to less than 2 or 3 millisecs, and rarely hiccups is to replace the Thread.sleep() call by a polling loop:

while ((System.nanoTime() / 100000L) < eventTime)
    ;

On the one hand, I feel embarrassed spending machine cycles like a drunken sailor on liberty. On the other hand, I am beginning to think there is no better way, and I should burn the machine cycles in the polling loop to reduce my latency, and meet my specification. Of course, when my app goes to the background, I pause my thread, so this polling loop works. But what a waste.

Any ideas would be greatly appreciated.

回答1:

I'm using delayed messages with Handler for similar purpose. It can be little overkill. In you case I would take a look to Timer class.

mTimer = new Timer();
mTimer.scheduleAtFixedRate(new TimerTask() {
    @Override
    public void run() {
        Log.v("TEST", " tick");
    }
}, 0, 250);

This gives me latency +-2 millisecs at emulator.