3

In Delphi 10.1 Berlin, I'm making an Android app. I created a timer like this:

fTimer := TTimer.Create(nil);
fTimer.Interval := 1;
fTimer.OnTimer := OnTimer;
fTimer.Enabled := True;

In the OnTimer event, I simply do this:

procedure TMyForm.OnTimer(Sender: TObject);
begin
  MyStopWatch.Stop;
  Inc(acounter);
  if acounter mod 1000 = 0 then 
    allog('delay', FloatToStr(xStopWatch.Elapsed.TotalMilliseconds));
  MyStopWatch := TStopWatch.StartNew;
end;

When I launch the app, the OnTimer event is fired every 10 ms instead of every 1 ms. However, if I touch the screen and move my finger around it, the event is fired every 1.3-1.5 ms instead.

Can someone explain this strange behavior to me?

Why is the app (or at least the timer) more reactive when my finger is touching the screen? How do I make the app always be this reactive?

About the remark of J..

it's not baterry life i think (but i m not sure) because if I use a thread instead of a timer like this :

TThread.createAnonymousThread(
  procedure
  var MyStopWatch: TstopWatch;
      acounter: integer;
  begin

    acounter := 0;
    MyStopWatch :=  TStopWatch.StartNew;

    while True do begin
      TThread.synchronize(nil,
        procedure
        begin
          MyStopWatch.Stop;
          Inc(acounter);
          if acounter mod 1000 = 0 then
            allog('delay', FloatToStr(MyStopWatch.Elapsed.TotalMilliseconds));
          MyStopWatch := TStopWatch.StartNew;
        end);
      sleep(1);
    END;

  end).start;

Then it's work ok, the event is fired every 2 ms (without TThread.synchronize every 1ms), and this finger or not on the screen.

andrey
  • 191
  • 8
  • 1
    Timers are *never* meant to be used to keep track of time - don't let the name fool you. The `Interval` is a *minimal* delay between executions. Timers in no way are locked down to exact times. If you have a time-sensitive task, use the system time, not the vague delay of a timer. – Jerry Dodge Aug 29 '17 at 22:47
  • @JerryDodge: I don't want a perfect precision, but between 1ms when the finger is on the screen to 10ms when no finger is on the screen their is a huge gap! this cause problem on scrolling for exemple (delphi scrolling calculation is made on the top of timer). But the real question: when the app is more reactive with a finger on the screen and most important how to make it always reactive like this ? – andrey Aug 29 '17 at 22:55
  • @Victoria: I don't understand? what research you made? I did also some research on this question, but find nothing a all. I only have this account ... – andrey Aug 29 '17 at 23:08
  • 3
    Mobile devices save power by only doing as much as *necessary*. When you're directly interacting with them they are more responsive. If you deliberately circumvent the platform's native power management strategies then you end up with an app that will kill the user's battery needlessly. There's a better way to solve your actual problem. – J... Aug 29 '17 at 23:14
  • @J... I just updated the question regarding your remark. can you see it ? – andrey Aug 29 '17 at 23:21

1 Answers1

11

Unlike VCL's TTimer, FMX's TTimer on Android is quite inefficient.

When the timer interval elapses, Android notifies FMX using a callback function (in the Androidapi.Timer unit). That callback is called by a worker thread, and it pushes the timer into a thread-safe queue (in the FMX.Platform.Android unit).

When the main UI thread checks for pending UI messages periodically, it also checks the timer queue, and if any timers are queued then their OnTimer event handlers are called (in the order that they have been queued).

However, if there are no pending UI messages, FMX may delay checking the timer queue! And then once all UI messages have been processed, there may be a delay before events are processed again. It all depends on the internal state of the FMX app.

So, there is no guarantee whatsoever that a 1 ms timer will fire its OnTimer event handler anywhere near 1 ms intervals. And it might also explain why an increase in UI activity can allow the OnTimer event to fire more often, because UI messages are being processed more often.

This differs from VCL's TTimer, which is based on the WM_TIMER UI message, which is a low-priority message that is only generated when there are no other UI messages pending. And when it is generated, it gets dispatched directly to the TTimer's internal window, there is no additional queuing involved to add extra layers of overhead. But, an increase in UI activity will slow down the TTImer.OnTimer event from firing, not speed it up.


In the case of TThread.Synchronize(), it actively notifies the main UI thread whenever a pending sync request needs to be handled, thus allowing the main UI thread to check for and execute sync'ed procedures sooner rather than later.

Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • dear @RemyLebeau, i don't understant, you say "However, if there are no pending UI messages, FMX may delay checking the timer queue!" ... i read the source code of TPlatformAndroid.InternalProcessMessages and it's seam that the TTimerManager.Current.CheckSynchronize is called on every loop, and it's not related to any UI messages ... the main loop is just repeat InternalProcessMessages until FAndroidApp^.destroyRequested <> 0; ... so as far as i understand nothing is delayed it ? – andrey Aug 30 '17 at 16:11
  • Maybe I'm wrong (I haven't debugged it to find out for sure), but the way I read the source for `InternalProcessMessages()`, the top of the loop waits with a timeout for a new UI message to arrive before the rest of the loop code is executed. And it is a low priority poll when FMX is not doing something with higher priority, so the poll may take time. That is why I think the timer queue gets delayed when the UI is not busy. – Remy Lebeau Aug 30 '17 at 16:20
  • me too didin't debug and just read the source code and i see that it's not waiting, but instead just "checking" if their is new ui message. if their is no new ui message then it's go to check synchronize. So in someway i can say that UI message normally even slow down the fire of the timer event – andrey Aug 30 '17 at 19:15
  • no sorry you are right !! it's wait 10ms for ui message ... dammed it's a so ugly think !!! – andrey Aug 30 '17 at 19:18
  • ok, now that i study a lot the think, it's not so ugly, in fact when their is NO ui event the delay is 10ms, but as soon as their is an event, the delay is drop to 0ms and the app wait 1000ms before to make it back to 10ms (i guess to not use all the batery). seam logical – andrey Sep 01 '17 at 16:36
  • i have one problem, can you say me how the TThread.Synchronize() can notify the main ui thread that a pending request is pending ? because as far as i understand synchronize event are call exactly the same way as timer event ... so how they can be prioritized ? – andrey Sep 03 '17 at 16:21
  • @andrey `TThread.Synchronize()` and `TThread.Queue()` both call `WakeMainThread()`, which posts a UI message to "wake up" the main thread. When the main thread performs messaging handling, the sync requests are executed. – Remy Lebeau Sep 03 '17 at 20:31
  • Thanks Remy, you definitively a delphi expert ! – andrey Sep 03 '17 at 20:43