Request DartPerformanceMode.latency
during transitions (#110600)
This commit is contained in:
parent
547de47a93
commit
d5f372bccd
@ -5,7 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:collection';
|
||||
import 'dart:developer' show Flow, Timeline, TimelineTask;
|
||||
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, PlatformDispatcher, TimingsCallback;
|
||||
import 'dart:ui' show AppLifecycleState, DartPerformanceMode, FramePhase, FrameTiming, PlatformDispatcher, TimingsCallback;
|
||||
|
||||
import 'package:collection/collection.dart' show HeapPriorityQueue, PriorityQueue;
|
||||
import 'package:flutter/foundation.dart';
|
||||
@ -183,6 +183,34 @@ enum SchedulerPhase {
|
||||
postFrameCallbacks,
|
||||
}
|
||||
|
||||
/// This callback is invoked when a request for [DartPerformanceMode] is disposed.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [PerformanceModeRequestHandle] for more information on the lifecycle of the handle.
|
||||
typedef _PerformanceModeCleaupCallback = VoidCallback;
|
||||
|
||||
/// An opaque handle that keeps a request for [DartPerformanceMode] active until
|
||||
/// disposed.
|
||||
///
|
||||
/// To create a [PerformanceModeRequestHandle], use [SchedulerBinding.requestPerformanceMode].
|
||||
/// The component that makes the request is responsible for disposing the handle.
|
||||
class PerformanceModeRequestHandle {
|
||||
PerformanceModeRequestHandle._(_PerformanceModeCleaupCallback this._cleanup);
|
||||
|
||||
_PerformanceModeCleaupCallback? _cleanup;
|
||||
|
||||
/// Call this method to signal to [SchedulerBinding] that a request for a [DartPerformanceMode]
|
||||
/// is no longer needed.
|
||||
///
|
||||
/// This method must only be called once per object.
|
||||
void dispose() {
|
||||
assert(_cleanup != null);
|
||||
_cleanup!();
|
||||
_cleanup = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Scheduler for running the following:
|
||||
///
|
||||
/// * _Transient callbacks_, triggered by the system's
|
||||
@ -605,6 +633,20 @@ mixin SchedulerBinding on BindingBase {
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Asserts that there are no pending performance mode requests in debug mode.
|
||||
///
|
||||
/// Throws a [FlutterError] if there are pending performance mode requests,
|
||||
/// as this indicates a potential memory leak.
|
||||
bool debugAssertNoPendingPerformanceModeRequests(String reason) {
|
||||
assert(() {
|
||||
if (_performanceMode != null) {
|
||||
throw FlutterError(reason);
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Prints the stack for where the current transient callback was registered.
|
||||
///
|
||||
/// A transient frame callback is one that was registered with
|
||||
@ -1085,6 +1127,59 @@ mixin SchedulerBinding on BindingBase {
|
||||
}
|
||||
}
|
||||
|
||||
DartPerformanceMode? _performanceMode;
|
||||
int _numPerformanceModeRequests = 0;
|
||||
|
||||
/// Request a specific [DartPerformanceMode].
|
||||
///
|
||||
/// Returns `null` if the request was not successful due to conflicting performance mode requests.
|
||||
/// Two requests are said to be in conflict if they are not of the same [DartPerformanceMode] type,
|
||||
/// and an explicit request for a performance mode has been made prior.
|
||||
///
|
||||
/// Requestor is responsible for calling [PerformanceModeRequestHandle.dispose] when it no longer
|
||||
/// requires the performance mode.
|
||||
PerformanceModeRequestHandle? requestPerformanceMode(DartPerformanceMode mode) {
|
||||
// conflicting requests are not allowed.
|
||||
if (_performanceMode != null && _performanceMode != mode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (_performanceMode == mode) {
|
||||
assert(_numPerformanceModeRequests > 0);
|
||||
_numPerformanceModeRequests++;
|
||||
} else if (_performanceMode == null) {
|
||||
assert(_numPerformanceModeRequests == 0);
|
||||
_performanceMode = mode;
|
||||
_numPerformanceModeRequests = 1;
|
||||
}
|
||||
|
||||
return PerformanceModeRequestHandle._(_disposePerformanceModeRequest);
|
||||
}
|
||||
|
||||
/// Remove a request for a specific [DartPerformanceMode].
|
||||
///
|
||||
/// If all the pending requests have been disposed, the engine will revert to the
|
||||
/// [DartPerformanceMode.balanced] performance mode.
|
||||
void _disposePerformanceModeRequest() {
|
||||
_numPerformanceModeRequests--;
|
||||
if (_numPerformanceModeRequests == 0) {
|
||||
_performanceMode = null;
|
||||
PlatformDispatcher.instance.requestDartPerformanceMode(DartPerformanceMode.balanced);
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the current [DartPerformanceMode] requested or `null` if no requests have
|
||||
/// been made.
|
||||
///
|
||||
/// This is only supported in debug and profile modes, returns `null` in release mode.
|
||||
DartPerformanceMode? debugGetRequestedPerformanceMode() {
|
||||
if (!(kDebugMode || kProfileMode)) {
|
||||
return null;
|
||||
} else {
|
||||
return _performanceMode;
|
||||
}
|
||||
}
|
||||
|
||||
/// Called by the engine to produce a new frame.
|
||||
///
|
||||
/// This method is called immediately after [handleBeginFrame]. It calls all
|
||||
|
@ -108,6 +108,14 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||
Future<T?> get completed => _transitionCompleter.future;
|
||||
final Completer<T?> _transitionCompleter = Completer<T?>();
|
||||
|
||||
/// Handle to the performance mode request.
|
||||
///
|
||||
/// When the route is animating, the performance mode is requested. It is then
|
||||
/// disposed when the animation ends. Requesting [DartPerformanceMode.latency]
|
||||
/// indicates to the engine that the transition is latency sensitive and to delay
|
||||
/// non-essential work while this handle is active.
|
||||
PerformanceModeRequestHandle? _performanceModeRequestHandle;
|
||||
|
||||
/// {@template flutter.widgets.TransitionRoute.transitionDuration}
|
||||
/// The duration the transition going forwards.
|
||||
///
|
||||
@ -221,12 +229,17 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||
if (overlayEntries.isNotEmpty) {
|
||||
overlayEntries.first.opaque = opaque;
|
||||
}
|
||||
_performanceModeRequestHandle?.dispose();
|
||||
_performanceModeRequestHandle = null;
|
||||
break;
|
||||
case AnimationStatus.forward:
|
||||
case AnimationStatus.reverse:
|
||||
if (overlayEntries.isNotEmpty) {
|
||||
overlayEntries.first.opaque = false;
|
||||
}
|
||||
_performanceModeRequestHandle ??=
|
||||
SchedulerBinding.instance
|
||||
.requestPerformanceMode(ui.DartPerformanceMode.latency);
|
||||
break;
|
||||
case AnimationStatus.dismissed:
|
||||
// We might still be an active route if a subclass is controlling the
|
||||
@ -236,6 +249,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||
if (!isActive) {
|
||||
navigator!.finalizeRoute(this);
|
||||
_popFinalized = true;
|
||||
_performanceModeRequestHandle?.dispose();
|
||||
_performanceModeRequestHandle = null;
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -465,6 +480,8 @@ abstract class TransitionRoute<T> extends OverlayRoute<T> {
|
||||
void dispose() {
|
||||
assert(!_transitionCompleter.isCompleted, 'Cannot dispose a $runtimeType twice.');
|
||||
_animation?.removeStatusListener(_handleStatusChanged);
|
||||
_performanceModeRequestHandle?.dispose();
|
||||
_performanceModeRequestHandle = null;
|
||||
if (willDisposeAnimationController) {
|
||||
_controller?.dispose();
|
||||
}
|
||||
|
@ -9,8 +9,11 @@
|
||||
// Fails with "flutter test --test-randomize-ordering-seed=456"
|
||||
@Tags(<String>['no-shuffle'])
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
Future<void> startTransitionBetween(
|
||||
@ -443,6 +446,26 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('DartPerformanceMode is latency mid-animation', (WidgetTester tester) async {
|
||||
DartPerformanceMode? mode;
|
||||
|
||||
// before the animation starts, no requests are active.
|
||||
mode = SchedulerBinding.instance.debugGetRequestedPerformanceMode();
|
||||
expect(mode, isNull);
|
||||
|
||||
await startTransitionBetween(tester, fromTitle: 'Page 1');
|
||||
|
||||
// mid-transition, latency mode is expected.
|
||||
await tester.pump(const Duration(milliseconds: 50));
|
||||
mode = SchedulerBinding.instance.debugGetRequestedPerformanceMode();
|
||||
expect(mode, equals(DartPerformanceMode.latency));
|
||||
|
||||
// end of transitio, go back to no requests active.
|
||||
await tester.pump(const Duration(milliseconds: 500));
|
||||
mode = SchedulerBinding.instance.debugGetRequestedPerformanceMode();
|
||||
expect(mode, isNull);
|
||||
});
|
||||
|
||||
testWidgets('Multiple nav bars tags do not conflict if in different navigators', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
CupertinoApp(
|
||||
|
56
packages/flutter/test/scheduler/performance_mode_test.dart
Normal file
56
packages/flutter/test/scheduler/performance_mode_test.dart
Normal file
@ -0,0 +1,56 @@
|
||||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
late SchedulerBinding binding;
|
||||
|
||||
setUpAll(() {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
binding = SchedulerBinding.instance;
|
||||
});
|
||||
|
||||
test('PerformanceModeHandler make one request', () async {
|
||||
final PerformanceModeRequestHandle? requestHandle = binding.requestPerformanceMode(DartPerformanceMode.latency);
|
||||
expect(requestHandle, isNotNull);
|
||||
expect(binding.debugGetRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
|
||||
requestHandle?.dispose();
|
||||
expect(binding.debugGetRequestedPerformanceMode(), isNull);
|
||||
});
|
||||
|
||||
test('PerformanceModeHandler make conflicting requests', () async {
|
||||
final PerformanceModeRequestHandle? requestHandle1 = binding.requestPerformanceMode(DartPerformanceMode.latency);
|
||||
expect(requestHandle1, isNotNull);
|
||||
|
||||
final PerformanceModeRequestHandle? requestHandle2 = binding.requestPerformanceMode(DartPerformanceMode.throughput);
|
||||
expect(requestHandle2, isNull);
|
||||
|
||||
expect(binding.debugGetRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
|
||||
|
||||
requestHandle1?.dispose();
|
||||
expect(binding.debugGetRequestedPerformanceMode(), isNull);
|
||||
});
|
||||
|
||||
test('PerformanceModeHandler revert only after last requestor disposed',
|
||||
() async {
|
||||
final PerformanceModeRequestHandle? requestHandle1 = binding.requestPerformanceMode(DartPerformanceMode.latency);
|
||||
expect(requestHandle1, isNotNull);
|
||||
|
||||
expect(binding.debugGetRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
|
||||
|
||||
final PerformanceModeRequestHandle? requestHandle2 = binding.requestPerformanceMode(DartPerformanceMode.latency);
|
||||
expect(requestHandle2, isNotNull);
|
||||
|
||||
expect(binding.debugGetRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
|
||||
requestHandle1?.dispose();
|
||||
expect(binding.debugGetRequestedPerformanceMode(), equals(DartPerformanceMode.latency));
|
||||
requestHandle2?.dispose();
|
||||
expect(binding.debugGetRequestedPerformanceMode(), isNull);
|
||||
});
|
||||
}
|
@ -885,6 +885,9 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
|
||||
assert(debugAssertNoTransientCallbacks(
|
||||
'An animation is still running even after the widget tree was disposed.'
|
||||
));
|
||||
assert(debugAssertNoPendingPerformanceModeRequests(
|
||||
'A performance mode was requested and not disposed by a test.'
|
||||
));
|
||||
assert(debugAssertAllFoundationVarsUnset(
|
||||
'The value of a foundation debug variable was changed by the test.',
|
||||
debugPrintOverride: debugPrintOverride,
|
||||
|
@ -39,6 +39,7 @@ Future<void> main() async {
|
||||
));
|
||||
expect(tester.binding, binding);
|
||||
binding.reportData = <String, dynamic>{'answer': 42};
|
||||
await tester.pump();
|
||||
});
|
||||
|
||||
testWidgets('hitTesting works when using setSurfaceSize', (WidgetTester tester) async {
|
||||
|
Loading…
x
Reference in New Issue
Block a user