Unverified Commit 40670c09 authored by liyuqian's avatar liyuqian Committed by GitHub

Allow multiple TimingsCallbacks (#43676)

This fixes https://github.com/flutter/flutter/issues/39277

The following tests cover this change:
- packages/flutter/test/foundation/service_extensions_test.dart
- packages/flutter/test/scheduler/scheduler_test.dart
parent 46edc802
......@@ -5,7 +5,7 @@
import 'dart:async';
import 'dart:collection';
import 'dart:developer' show Flow, Timeline;
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming;
import 'dart:ui' show AppLifecycleState, FramePhase, FrameTiming, TimingsCallback;
import 'package:collection/collection.dart' show PriorityQueue, HeapPriorityQueue;
import 'package:flutter/foundation.dart';
......@@ -203,15 +203,69 @@ mixin SchedulerBinding on BindingBase, ServicesBinding {
if (!kReleaseMode) {
int frameNumber = 0;
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
window.onReportTimings = (List<FrameTiming> timings) {
addTimingsCallback((List<FrameTiming> timings) {
for (FrameTiming frameTiming in timings) {
frameNumber += 1;
_profileFramePostEvent(frameNumber, frameTiming);
}
};
});
}
}
final List<TimingsCallback> _timingsCallbacks = <TimingsCallback>[];
/// Add a [TimingsCallback] that receives [FrameTiming] sent from the engine.
///
/// This can be used, for example, to monitor the performance in release mode,
/// or to get a signal when the first frame is rasterized.
///
/// This is preferred over using [Window.onReportTimings] directly because
/// [addTimingsCallback] allows multiple callbacks.
///
/// If the same callback is added twice, it will be executed twice.
void addTimingsCallback(TimingsCallback callback) {
// TODO(liyuqian): once this is merged, modify the doc of
// [Window.onReportTimings] inside the engine repo to recommend using this
// API instead of using [Window.onReportTimings] directly.
_timingsCallbacks.add(callback);
if (_timingsCallbacks.length == 1) {
assert(window.onReportTimings == null);
window.onReportTimings = _executeTimingsCallbacks;
}
assert(window.onReportTimings == _executeTimingsCallbacks);
}
/// Removes a callback that was earlier added by [addTimingsCallback].
void removeTimingsCallback(TimingsCallback callback) {
assert(_timingsCallbacks.contains(callback));
_timingsCallbacks.remove(callback);
if (_timingsCallbacks.isEmpty) {
window.onReportTimings = null;
}
}
void _executeTimingsCallbacks(List<FrameTiming> timings) {
final List<TimingsCallback> clonedCallbacks =
List<TimingsCallback>.from(_timingsCallbacks);
for (TimingsCallback callback in clonedCallbacks) {
try {
if (_timingsCallbacks.contains(callback)) {
callback(timings);
}
} catch (exception, stack) {
FlutterError.reportError(FlutterErrorDetails(
exception: exception,
stack: stack,
context: ErrorDescription('while executing callbacks for FrameTiming'),
informationCollector: () sync* {
yield DiagnosticsProperty<TimingsCallback>(
'The TimingsCallback that gets executed was',
callback,
style: DiagnosticsTreeStyle.errorProperty,
);
},
));
}
}
}
......
......@@ -755,25 +755,17 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
if (_needToReportFirstFrame && _reportFirstFrame) {
assert(!_firstFrameCompleter.isCompleted);
// TODO(liyuqian): use a broadcast stream approach
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
final TimingsCallback oldCallback = WidgetsBinding.instance.window.onReportTimings;
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
WidgetsBinding.instance.window.onReportTimings = (List<FrameTiming> timings) {
TimingsCallback firstFrameCallback;
firstFrameCallback = (List<FrameTiming> timings) {
if (!kReleaseMode) {
developer.Timeline.instantSync('Rasterized first useful frame');
developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
}
if (oldCallback != null) {
oldCallback(timings);
}
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
WidgetsBinding.instance.window.onReportTimings = oldCallback;
SchedulerBinding.instance.removeTimingsCallback(firstFrameCallback);
_firstFrameCompleter.complete();
};
SchedulerBinding.instance.addTimingsCallback(firstFrameCallback);
}
try {
......
......@@ -78,11 +78,7 @@ class TestServiceExtensionsBinding extends BindingBase
await flushMicrotasks();
if (ui.window.onDrawFrame != null)
ui.window.onDrawFrame();
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
if (ui.window.onReportTimings != null)
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
ui.window.onReportTimings(<ui.FrameTiming>[]);
}
......
......@@ -132,8 +132,6 @@ void main() {
});
test('Flutter.Frame event fired', () async {
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
window.onReportTimings(<FrameTiming>[FrameTiming(<int>[
// build start, build finish
10000, 15000,
......@@ -152,6 +150,18 @@ void main() {
expect(event['raster'], 4000);
});
test('TimingsCallback exceptions are caught', () {
FlutterErrorDetails errorCaught;
FlutterError.onError = (FlutterErrorDetails details) {
errorCaught = details;
};
SchedulerBinding.instance.addTimingsCallback((List<FrameTiming> timings) {
throw Exception('Test');
});
window.onReportTimings(<FrameTiming>[]);
expect(errorCaught.exceptionAsString(), equals('Exception: Test'));
});
test('currentSystemFrameTimeStamp is the raw timestamp', () {
Duration lastTimeStamp;
Duration lastSystemTimeStamp;
......
......@@ -286,13 +286,9 @@ class TestWindow implements Window {
}
@override
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
TimingsCallback get onReportTimings => _window.onReportTimings;
@override
set onReportTimings(TimingsCallback callback) {
// use frameTimings. https://github.com/flutter/flutter/issues/38838
// ignore: deprecated_member_use
_window.onReportTimings = callback;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment