diff --git a/packages/flutter/lib/src/scheduler/binding.dart b/packages/flutter/lib/src/scheduler/binding.dart index 7d4af952a86..4cfe596bf8c 100644 --- a/packages/flutter/lib/src/scheduler/binding.dart +++ b/packages/flutter/lib/src/scheduler/binding.dart @@ -1025,28 +1025,29 @@ mixin SchedulerBinding on BindingBase { debugTimelineTask = TimelineTask()..start('Warm-up frame'); } final bool hadScheduledFrame = _hasScheduledFrame; - // We use timers here to ensure that microtasks flush in between. - Timer.run(() { - assert(_warmUpFrame); - handleBeginFrame(null); - }); - Timer.run(() { - assert(_warmUpFrame); - handleDrawFrame(); - // We call resetEpoch after this frame so that, in the hot reload case, - // the very next frame pretends to have occurred immediately after this - // warm-up frame. The warm-up frame's timestamp will typically be far in - // the past (the time of the last real frame), so if we didn't reset the - // epoch we would see a sudden jump from the old time in the warm-up frame - // to the new time in the "real" frame. The biggest problem with this is - // that implicit animations end up being triggered at the old time and - // then skipping every frame and finishing in the new time. - resetEpoch(); - _warmUpFrame = false; - if (hadScheduledFrame) { - scheduleFrame(); - } - }); + PlatformDispatcher.instance.scheduleWarmUpFrame( + beginFrame: () { + assert(_warmUpFrame); + handleBeginFrame(null); + }, + drawFrame: () { + assert(_warmUpFrame); + handleDrawFrame(); + // We call resetEpoch after this frame so that, in the hot reload case, + // the very next frame pretends to have occurred immediately after this + // warm-up frame. The warm-up frame's timestamp will typically be far in + // the past (the time of the last real frame), so if we didn't reset the + // epoch we would see a sudden jump from the old time in the warm-up frame + // to the new time in the "real" frame. The biggest problem with this is + // that implicit animations end up being triggered at the old time and + // then skipping every frame and finishing in the new time. + resetEpoch(); + _warmUpFrame = false; + if (hadScheduledFrame) { + scheduleFrame(); + } + }, + ); // Lock events so touch events etc don't insert themselves until the // scheduled frame has finished. diff --git a/packages/flutter/test/scheduler/scheduler_test.dart b/packages/flutter/test/scheduler/scheduler_test.dart index 098cf6ec1c0..7afed98aebf 100644 --- a/packages/flutter/test/scheduler/scheduler_test.dart +++ b/packages/flutter/test/scheduler/scheduler_test.dart @@ -14,6 +14,21 @@ import 'scheduler_tester.dart'; class TestSchedulerBinding extends BindingBase with SchedulerBinding, ServicesBinding { final Map>> eventsDispatched = >>{}; + VoidCallback? additionalHandleBeginFrame; + VoidCallback? additionalHandleDrawFrame; + + @override + void handleBeginFrame(Duration? rawTimeStamp) { + additionalHandleBeginFrame?.call(); + super.handleBeginFrame(rawTimeStamp); + } + + @override + void handleDrawFrame() { + additionalHandleDrawFrame?.call(); + super.handleDrawFrame(); + } + @override void postEvent(String eventKind, Map eventData) { getEventsDispatched(eventKind).add(eventData); @@ -39,6 +54,11 @@ void main() { scheduler = TestSchedulerBinding(); }); + tearDown(() { + scheduler.additionalHandleBeginFrame = null; + scheduler.additionalHandleDrawFrame = null; + }); + test('Tasks are executed in the right order', () { final TestStrategy strategy = TestStrategy(); scheduler.schedulingStrategy = strategy.shouldRunTaskWithPriority; @@ -111,6 +131,25 @@ void main() { expect(executedTasks[0], equals(0)); }); + test('scheduleWarmUpFrame should flush microtasks between callbacks', () async { + addTearDown(() => scheduler.handleEventLoopCallback()); + + bool microtaskDone = false; + final Completer drawFrameDone = Completer(); + scheduler.additionalHandleBeginFrame = () { + expect(microtaskDone, false); + scheduleMicrotask(() { + microtaskDone = true; + }); + }; + scheduler.additionalHandleDrawFrame = () { + expect(microtaskDone, true); + drawFrameDone.complete(); + }; + scheduler.scheduleWarmUpFrame(); + await drawFrameDone.future; + }); + test('2 calls to scheduleWarmUpFrame just schedules it once', () { final List timerQueueTasks = []; bool taskExecuted = false;