Image tracing (#50648)
This commit is contained in:
parent
9431229e4c
commit
766bd70598
@ -457,6 +457,11 @@ Future<void> _runFrameworkTests() async {
|
|||||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
|
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_localizations'), tableData: bigqueryApi?.tabledata);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata);
|
await _runFlutterTest(path.join(flutterRoot, 'packages', 'flutter_test'), tableData: bigqueryApi?.tabledata);
|
||||||
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
|
await _runFlutterTest(path.join(flutterRoot, 'packages', 'fuchsia_remote_debug_protocol'), tableData: bigqueryApi?.tabledata);
|
||||||
|
await _runFlutterTest(
|
||||||
|
path.join(flutterRoot, 'dev', 'tracing_tests'),
|
||||||
|
options: <String>['--enable-vmservice'],
|
||||||
|
tableData: bigqueryApi?.tabledata,
|
||||||
|
);
|
||||||
await _runFlutterTest(
|
await _runFlutterTest(
|
||||||
path.join(flutterRoot, 'dev', 'integration_tests', 'codegen'),
|
path.join(flutterRoot, 'dev', 'integration_tests', 'codegen'),
|
||||||
tableData: bigqueryApi?.tabledata,
|
tableData: bigqueryApi?.tabledata,
|
||||||
|
@ -17,7 +17,7 @@ dev_dependencies:
|
|||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
||||||
e2e: 0.2.3+1
|
e2e: 0.2.4+1
|
||||||
|
|
||||||
archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
@ -40,4 +40,4 @@ dev_dependencies:
|
|||||||
test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
|
||||||
# PUBSPEC CHECKSUM: bb2f
|
# PUBSPEC CHECKSUM: bc30
|
||||||
|
7
dev/tracing_tests/README.md
Normal file
7
dev/tracing_tests/README.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# Tracing tests
|
||||||
|
|
||||||
|
The tests in this folder must be run with `flutter test --enable-vmservice`,
|
||||||
|
since they test that trace data is written to the timeline by connecting to
|
||||||
|
the observatory.
|
||||||
|
|
||||||
|
These tests will fail if run without this flag.
|
42
dev/tracing_tests/pubspec.yaml
Normal file
42
dev/tracing_tests/pubspec.yaml
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
name: tracing_tests
|
||||||
|
description: Various tests for tracing in flutter/flutter
|
||||||
|
|
||||||
|
environment:
|
||||||
|
# The pub client defaults to an <2.0.0 sdk constraint which we need to explicitly overwrite.
|
||||||
|
sdk: ">=2.2.2 <3.0.0"
|
||||||
|
|
||||||
|
dependencies:
|
||||||
|
flutter:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
collection: 1.14.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
meta: 1.1.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
typed_data: 1.1.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
vector_math: 2.0.8 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
|
||||||
|
dev_dependencies:
|
||||||
|
flutter_test:
|
||||||
|
sdk: flutter
|
||||||
|
|
||||||
|
archive: 2.0.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
args: 1.5.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
async: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
boolean_selector: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
charcode: 1.1.2 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
convert: 2.1.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
crypto: 2.1.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
image: 2.1.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
matcher: 0.12.6 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
path: 1.6.4 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
pedantic: 1.8.0+1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
petitparser: 2.4.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
quiver: 2.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
source_span: 1.5.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
stack_trace: 1.9.3 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
stream_channel: 2.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
string_scanner: 1.0.5 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
term_glyph: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
test_api: 0.2.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
xml: 3.5.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||||
|
|
||||||
|
# PUBSPEC CHECKSUM: f789
|
160
dev/tracing_tests/test/image_cache_tracing_test.dart
Normal file
160
dev/tracing_tests/test/image_cache_tracing_test.dart
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
// 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:async';
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer' as developer;
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:isolate' as isolate;
|
||||||
|
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
String isolateId;
|
||||||
|
final TimelineObtainer timelineObtainer = TimelineObtainer();
|
||||||
|
|
||||||
|
setUpAll(() async {
|
||||||
|
isolateId = developer.Service.getIsolateID(isolate.Isolate.current);
|
||||||
|
final developer.ServiceProtocolInfo info = await developer.Service.getInfo();
|
||||||
|
|
||||||
|
if (info.serverUri == null) {
|
||||||
|
throw TestFailure('This test _must_ be run with --enable-vmservice.');
|
||||||
|
}
|
||||||
|
await timelineObtainer.connect(info.serverUri);
|
||||||
|
await timelineObtainer.setDartFlags();
|
||||||
|
|
||||||
|
// Initialize the image cache.
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await timelineObtainer?.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Image cache tracing', () async {
|
||||||
|
final TestImageStreamCompleter completer1 = TestImageStreamCompleter();
|
||||||
|
PaintingBinding.instance.imageCache.putIfAbsent(
|
||||||
|
'Test',
|
||||||
|
() => completer1,
|
||||||
|
);
|
||||||
|
PaintingBinding.instance.imageCache.clear();
|
||||||
|
|
||||||
|
final List<Map<String, dynamic>> timelineEvents = await timelineObtainer.getTimelineData();
|
||||||
|
|
||||||
|
_expectTimelineEvents(
|
||||||
|
timelineEvents,
|
||||||
|
<Map<String, dynamic>>[
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': 'ImageCache.putIfAbsent',
|
||||||
|
'args': <String, dynamic>{'key': 'Test', 'isolateId': isolateId}
|
||||||
|
},
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': 'listener',
|
||||||
|
'args': <String, dynamic>{'parentId': '1', 'isolateId': isolateId}
|
||||||
|
},
|
||||||
|
<String, dynamic>{
|
||||||
|
'name': 'ImageCache.clear',
|
||||||
|
'args': <String, dynamic>{
|
||||||
|
'pendingImages': 1,
|
||||||
|
'cachedImages': 0,
|
||||||
|
'currentSizeInBytes': 0,
|
||||||
|
'isolateId': isolateId,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}, skip: isBrowser); // uses dart:isolate and io
|
||||||
|
}
|
||||||
|
|
||||||
|
void _expectTimelineEvents(
|
||||||
|
List<Map<String, dynamic>> events,
|
||||||
|
List<Map<String, dynamic>> expected,
|
||||||
|
) {
|
||||||
|
for (final Map<String, dynamic> event in events) {
|
||||||
|
|
||||||
|
for (int index = 0; index < expected.length; index += 1) {
|
||||||
|
if (expected[index]['name'] == event['name']) {
|
||||||
|
final Map<String, dynamic> expectedArgs = expected[index]['args'] as Map<String, dynamic>;
|
||||||
|
final Map<String, dynamic> args = event['args'] as Map<String, dynamic>;
|
||||||
|
if (_mapsEqual(expectedArgs, args)) {
|
||||||
|
expected.removeAt(index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (expected.isNotEmpty) {
|
||||||
|
final String encodedEvents = jsonEncode(events);
|
||||||
|
fail('Timeline did not contain expected events: $expected\nactual: $encodedEvents');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _mapsEqual(Map<String, dynamic> expectedArgs, Map<String, dynamic> args) {
|
||||||
|
if (expectedArgs.length != args.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (final String key in expectedArgs.keys) {
|
||||||
|
if (expectedArgs[key] != args[key]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(dnfield): we can drop this in favor of vm_service when https://github.com/dart-lang/webdev/issues/899 is resolved.
|
||||||
|
class TimelineObtainer {
|
||||||
|
WebSocket _observatorySocket;
|
||||||
|
int _lastCallId = 0;
|
||||||
|
|
||||||
|
final Map<int, Completer<dynamic>> _completers = <int, Completer<dynamic>>{};
|
||||||
|
|
||||||
|
|
||||||
|
Future<void> connect(Uri uri) async {
|
||||||
|
_observatorySocket = await WebSocket.connect('ws://localhost:${uri.port}${uri.path}ws');
|
||||||
|
_observatorySocket.listen((dynamic data) => _processResponse(data as String));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _processResponse(String data) {
|
||||||
|
final Map<String, dynamic> json = jsonDecode(data) as Map<String, dynamic>;
|
||||||
|
final int id = json['id'] as int;
|
||||||
|
_completers.remove(id).complete(json['result']);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> setDartFlags() async {
|
||||||
|
_lastCallId += 1;
|
||||||
|
final Completer<Map<String, dynamic>> completer = Completer<Map<String, dynamic>>();
|
||||||
|
_completers[_lastCallId] = completer;
|
||||||
|
_observatorySocket.add(jsonEncode(<String, dynamic>{
|
||||||
|
'id': _lastCallId,
|
||||||
|
'method': 'setVMTimelineFlags',
|
||||||
|
'params': <String, dynamic>{
|
||||||
|
'recordedStreams': <String>['Dart'],
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
final Map<String, dynamic> result = await completer.future;
|
||||||
|
return result['type'] == 'Success';
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<Map<String, dynamic>>> getTimelineData() async {
|
||||||
|
_lastCallId += 1;
|
||||||
|
final Completer<Map<String, dynamic>> completer = Completer<Map<String, dynamic>>();
|
||||||
|
_completers[_lastCallId] = completer;
|
||||||
|
_observatorySocket.add(jsonEncode(<String, dynamic>{
|
||||||
|
'id': _lastCallId,
|
||||||
|
'method': 'getVMTimeline',
|
||||||
|
}));
|
||||||
|
|
||||||
|
final Map<String, dynamic> result = await completer.future;
|
||||||
|
final List<dynamic> list = result['traceEvents'] as List<dynamic>;
|
||||||
|
return list.cast<Map<String, dynamic>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> close() async {
|
||||||
|
expect(_completers, isEmpty);
|
||||||
|
await _observatorySocket.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class TestImageStreamCompleter extends ImageStreamCompleter {}
|
@ -2,6 +2,10 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:developer';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
import 'image_stream.dart';
|
import 'image_stream.dart';
|
||||||
|
|
||||||
const int _kDefaultSize = 1000;
|
const int _kDefaultSize = 1000;
|
||||||
@ -85,11 +89,21 @@ class ImageCache {
|
|||||||
assert(value >= 0);
|
assert(value >= 0);
|
||||||
if (value == maximumSize)
|
if (value == maximumSize)
|
||||||
return;
|
return;
|
||||||
|
TimelineTask timelineTask;
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask = TimelineTask()..start(
|
||||||
|
'ImageCache.setMaximumSize',
|
||||||
|
arguments: <String, dynamic>{'value': value},
|
||||||
|
);
|
||||||
|
}
|
||||||
_maximumSize = value;
|
_maximumSize = value;
|
||||||
if (maximumSize == 0) {
|
if (maximumSize == 0) {
|
||||||
clear();
|
clear();
|
||||||
} else {
|
} else {
|
||||||
_checkCacheSize();
|
_checkCacheSize(timelineTask);
|
||||||
|
}
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -114,11 +128,21 @@ class ImageCache {
|
|||||||
assert(value >= 0);
|
assert(value >= 0);
|
||||||
if (value == _maximumSizeBytes)
|
if (value == _maximumSizeBytes)
|
||||||
return;
|
return;
|
||||||
|
TimelineTask timelineTask;
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask = TimelineTask()..start(
|
||||||
|
'ImageCache.setMaximumSizeBytes',
|
||||||
|
arguments: <String, dynamic>{'value': value},
|
||||||
|
);
|
||||||
|
}
|
||||||
_maximumSizeBytes = value;
|
_maximumSizeBytes = value;
|
||||||
if (_maximumSizeBytes == 0) {
|
if (_maximumSizeBytes == 0) {
|
||||||
clear();
|
clear();
|
||||||
} else {
|
} else {
|
||||||
_checkCacheSize();
|
_checkCacheSize(timelineTask);
|
||||||
|
}
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask.finish();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -134,6 +158,16 @@ class ImageCache {
|
|||||||
/// Images which have not finished loading yet will not be removed from the
|
/// Images which have not finished loading yet will not be removed from the
|
||||||
/// cache, and when they complete they will be inserted as normal.
|
/// cache, and when they complete they will be inserted as normal.
|
||||||
void clear() {
|
void clear() {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
Timeline.instantSync(
|
||||||
|
'ImageCache.clear',
|
||||||
|
arguments: <String, dynamic>{
|
||||||
|
'pendingImages': _pendingImages.length,
|
||||||
|
'cachedImages': _cache.length,
|
||||||
|
'currentSizeInBytes': _currentSizeBytes,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
_cache.clear();
|
_cache.clear();
|
||||||
_pendingImages.clear();
|
_pendingImages.clear();
|
||||||
_currentSizeBytes = 0;
|
_currentSizeBytes = 0;
|
||||||
@ -158,14 +192,30 @@ class ImageCache {
|
|||||||
bool evict(Object key) {
|
bool evict(Object key) {
|
||||||
final _PendingImage pendingImage = _pendingImages.remove(key);
|
final _PendingImage pendingImage = _pendingImages.remove(key);
|
||||||
if (pendingImage != null) {
|
if (pendingImage != null) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
|
||||||
|
'type': 'pending'
|
||||||
|
});
|
||||||
|
}
|
||||||
pendingImage.removeListener();
|
pendingImage.removeListener();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
final _CachedImage image = _cache.remove(key);
|
final _CachedImage image = _cache.remove(key);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
|
||||||
|
'type': 'completed',
|
||||||
|
'sizeiInBytes': image.sizeBytes,
|
||||||
|
});
|
||||||
|
}
|
||||||
_currentSizeBytes -= image.sizeBytes;
|
_currentSizeBytes -= image.sizeBytes;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
|
||||||
|
'type': 'miss',
|
||||||
|
});
|
||||||
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,20 +232,45 @@ class ImageCache {
|
|||||||
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
|
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
|
||||||
assert(key != null);
|
assert(key != null);
|
||||||
assert(loader != null);
|
assert(loader != null);
|
||||||
|
TimelineTask timelineTask;
|
||||||
|
TimelineTask listenerTask;
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask = TimelineTask()..start(
|
||||||
|
'ImageCache.putIfAbsent',
|
||||||
|
arguments: <String, dynamic>{
|
||||||
|
'key': key.toString(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
ImageStreamCompleter result = _pendingImages[key]?.completer;
|
ImageStreamCompleter result = _pendingImages[key]?.completer;
|
||||||
// Nothing needs to be done because the image hasn't loaded yet.
|
// Nothing needs to be done because the image hasn't loaded yet.
|
||||||
if (result != null)
|
if (result != null) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask.finish(arguments: <String, dynamic>{'result': 'pending'});
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
|
}
|
||||||
// Remove the provider from the list so that we can move it to the
|
// Remove the provider from the list so that we can move it to the
|
||||||
// recently used position below.
|
// recently used position below.
|
||||||
final _CachedImage image = _cache.remove(key);
|
final _CachedImage image = _cache.remove(key);
|
||||||
if (image != null) {
|
if (image != null) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask.finish(arguments: <String, dynamic>{'result': 'completed'});
|
||||||
|
}
|
||||||
_cache[key] = image;
|
_cache[key] = image;
|
||||||
return image.completer;
|
return image.completer;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
result = loader();
|
result = loader();
|
||||||
} catch (error, stackTrace) {
|
} catch (error, stackTrace) {
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
timelineTask.finish(arguments: <String, dynamic>{
|
||||||
|
'result': 'error',
|
||||||
|
'error': error.toString(),
|
||||||
|
'stackTrace': stackTrace.toString(),
|
||||||
|
});
|
||||||
|
}
|
||||||
if (onError != null) {
|
if (onError != null) {
|
||||||
onError(error, stackTrace);
|
onError(error, stackTrace);
|
||||||
return null;
|
return null;
|
||||||
@ -203,6 +278,11 @@ class ImageCache {
|
|||||||
rethrow;
|
rethrow;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
listenerTask = TimelineTask(parent: timelineTask)..start('listener');
|
||||||
|
}
|
||||||
|
bool listenedOnce = false;
|
||||||
void listener(ImageInfo info, bool syncCall) {
|
void listener(ImageInfo info, bool syncCall) {
|
||||||
// Images that fail to load don't contribute to cache size.
|
// Images that fail to load don't contribute to cache size.
|
||||||
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
|
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
|
||||||
@ -215,8 +295,19 @@ class ImageCache {
|
|||||||
if (imageSize <= maximumSizeBytes) {
|
if (imageSize <= maximumSizeBytes) {
|
||||||
_currentSizeBytes += imageSize;
|
_currentSizeBytes += imageSize;
|
||||||
_cache[key] = image;
|
_cache[key] = image;
|
||||||
_checkCacheSize();
|
_checkCacheSize(listenerTask);
|
||||||
}
|
}
|
||||||
|
if (!kReleaseMode && !listenedOnce) {
|
||||||
|
listenerTask.finish(arguments: <String, dynamic>{
|
||||||
|
'syncCall': syncCall,
|
||||||
|
'sizeInBytes': imageSize,
|
||||||
|
});
|
||||||
|
timelineTask.finish(arguments: <String, dynamic>{
|
||||||
|
'currentSizeBytes': currentSizeBytes,
|
||||||
|
'currentSize': currentSize,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
listenedOnce = true;
|
||||||
}
|
}
|
||||||
if (maximumSize > 0 && maximumSizeBytes > 0) {
|
if (maximumSize > 0 && maximumSizeBytes > 0) {
|
||||||
final ImageStreamListener streamListener = ImageStreamListener(listener);
|
final ImageStreamListener streamListener = ImageStreamListener(listener);
|
||||||
@ -234,12 +325,28 @@ class ImageCache {
|
|||||||
|
|
||||||
// Remove images from the cache until both the length and bytes are below
|
// Remove images from the cache until both the length and bytes are below
|
||||||
// maximum, or the cache is empty.
|
// maximum, or the cache is empty.
|
||||||
void _checkCacheSize() {
|
void _checkCacheSize(TimelineTask timelineTask) {
|
||||||
|
final Map<String, dynamic> finishArgs = <String, dynamic>{};
|
||||||
|
TimelineTask checkCacheTask;
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
checkCacheTask = TimelineTask(parent: timelineTask)..start('checkCacheSize');
|
||||||
|
finishArgs['evictedKeys'] = <String>[];
|
||||||
|
finishArgs['currentSize'] = currentSize;
|
||||||
|
finishArgs['currentSizeBytes'] = currentSizeBytes;
|
||||||
|
}
|
||||||
while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
|
while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
|
||||||
final Object key = _cache.keys.first;
|
final Object key = _cache.keys.first;
|
||||||
final _CachedImage image = _cache[key];
|
final _CachedImage image = _cache[key];
|
||||||
_currentSizeBytes -= image.sizeBytes;
|
_currentSizeBytes -= image.sizeBytes;
|
||||||
_cache.remove(key);
|
_cache.remove(key);
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
finishArgs['evictedKeys'].add(key.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!kReleaseMode) {
|
||||||
|
finishArgs['endSize'] = currentSize;
|
||||||
|
finishArgs['endSizeBytes'] = currentSizeBytes;
|
||||||
|
checkCacheTask.finish(arguments: finishArgs);
|
||||||
}
|
}
|
||||||
assert(_currentSizeBytes >= 0);
|
assert(_currentSizeBytes >= 0);
|
||||||
assert(_cache.length <= maximumSize);
|
assert(_cache.length <= maximumSize);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user