Skip to content

Commit 2f09d60

Browse files
authored
Revert "Live image cache (flutter#50318)" (flutter#51131)
This reverts commit 1602be6.
1 parent 25a8131 commit 2f09d60

File tree

10 files changed

+50
-688
lines changed

10 files changed

+50
-688
lines changed

dev/tracing_tests/test/image_cache_tracing_test.dart

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ void main() {
2020
final developer.ServiceProtocolInfo info = await developer.Service.getInfo();
2121

2222
if (info.serverUri == null) {
23-
fail('This test _must_ be run with --enable-vmservice.');
23+
throw TestFailure('This test _must_ be run with --enable-vmservice.');
2424
}
2525
await timelineObtainer.connect(info.serverUri);
2626
await timelineObtainer.setDartFlags();
@@ -58,8 +58,7 @@ void main() {
5858
'name': 'ImageCache.clear',
5959
'args': <String, dynamic>{
6060
'pendingImages': 1,
61-
'keepAliveImages': 0,
62-
'liveImages': 1,
61+
'cachedImages': 0,
6362
'currentSizeInBytes': 0,
6463
'isolateId': isolateId,
6564
}
@@ -154,7 +153,7 @@ class TimelineObtainer {
154153

155154
Future<void> close() async {
156155
expect(_completers, isEmpty);
157-
await _observatorySocket?.close();
156+
await _observatorySocket.close();
158157
}
159158
}
160159

packages/flutter/lib/src/painting/binding.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,6 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
9696
void evict(String asset) {
9797
super.evict(asset);
9898
imageCache.clear();
99-
imageCache.clearLiveImages();
10099
}
101100

102101
/// Listenable that notifies when the available fonts on the system have

packages/flutter/lib/src/painting/image_cache.dart

Lines changed: 18 additions & 195 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
// found in the LICENSE file.
44

55
import 'dart:developer';
6-
import 'dart:ui' show hashValues;
76

87
import 'package:flutter/foundation.dart';
98

@@ -16,24 +15,18 @@ const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
1615
///
1716
/// Implements a least-recently-used cache of up to 1000 images, and up to 100
1817
/// MB. The maximum size can be adjusted using [maximumSize] and
19-
/// [maximumSizeBytes].
20-
///
21-
/// The cache also holds a list of "live" references. An image is considered
22-
/// live if its [ImageStreamCompleter]'s listener count has never dropped to
23-
/// zero after adding at least one listener. The cache uses
24-
/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] to determine when
25-
/// this has happened.
18+
/// [maximumSizeBytes]. Images that are actively in use (i.e. to which the
19+
/// application is holding references, either via [ImageStream] objects,
20+
/// [ImageStreamCompleter] objects, [ImageInfo] objects, or raw [dart:ui.Image]
21+
/// objects) may get evicted from the cache (and thus need to be refetched from
22+
/// the network if they are referenced in the [putIfAbsent] method), but the raw
23+
/// bits are kept in memory for as long as the application is using them.
2624
///
2725
/// The [putIfAbsent] method is the main entry-point to the cache API. It
2826
/// returns the previously cached [ImageStreamCompleter] for the given key, if
2927
/// available; if not, it calls the given callback to obtain it first. In either
3028
/// case, the key is moved to the "most recently used" position.
3129
///
32-
/// A caller can determine whether an image is already in the cache by using
33-
/// [containsKey], which will return true if the image is tracked by the cache
34-
/// in a pending or compelted state. More fine grained information is available
35-
/// by using the [statusForKey] method.
36-
///
3730
/// Generally this class is not used directly. The [ImageProvider] class and its
3831
/// subclasses automatically handle the caching of images.
3932
///
@@ -78,11 +71,6 @@ const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
7871
class ImageCache {
7972
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
8073
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
81-
/// ImageStreamCompleters with at least one listener. These images may or may
82-
/// not fit into the _pendingImages or _cache objects.
83-
///
84-
/// Unlike _cache, the [_CachedImage] for this may have a null byte size.
85-
final Map<Object, _CachedImage> _liveImages = <Object, _CachedImage>{};
8674

8775
/// Maximum number of entries to store in the cache.
8876
///
@@ -175,8 +163,7 @@ class ImageCache {
175163
'ImageCache.clear',
176164
arguments: <String, dynamic>{
177165
'pendingImages': _pendingImages.length,
178-
'keepAliveImages': _cache.length,
179-
'liveImages': _liveImages.length,
166+
'cachedImages': _cache.length,
180167
'currentSizeInBytes': _currentSizeBytes,
181168
},
182169
);
@@ -217,7 +204,7 @@ class ImageCache {
217204
if (image != null) {
218205
if (!kReleaseMode) {
219206
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
220-
'type': 'keepAlive',
207+
'type': 'completed',
221208
'sizeiInBytes': image.sizeBytes,
222209
});
223210
}
@@ -232,36 +219,6 @@ class ImageCache {
232219
return false;
233220
}
234221

235-
/// Updates the least recently used image cache with this image, if it is
236-
/// less than the [maximumSizeBytes] of this cache.
237-
///
238-
/// Resizes the cache as appropriate to maintain the constraints of
239-
/// [maximumSize] and [maximumSizeBytes].
240-
void _touch(Object key, _CachedImage image, TimelineTask timelineTask) {
241-
assert(timelineTask != null);
242-
if (image.sizeBytes != null && image.sizeBytes <= maximumSizeBytes) {
243-
_currentSizeBytes += image.sizeBytes;
244-
_cache[key] = image;
245-
_checkCacheSize(timelineTask);
246-
}
247-
}
248-
249-
void _trackLiveImage(Object key, _CachedImage image) {
250-
// Avoid adding unnecessary callbacks to the completer.
251-
if (_liveImages.containsKey(key)) {
252-
assert(identical(_liveImages[key].completer, image.completer));
253-
return;
254-
}
255-
_liveImages[key] = image;
256-
// Even if no callers to ImageProvider.resolve have listened to the stream,
257-
// the cache is listening to the stream and will remove itself once the
258-
// image completes to move it from pending to keepAlive.
259-
// Even if the cache size is 0, we still add this listener.
260-
image.completer.addOnLastListenerRemovedCallback(() {
261-
_liveImages.remove(key);
262-
});
263-
}
264-
265222
/// Returns the previously cached [ImageStream] for the given key, if available;
266223
/// if not, calls the given callback to obtain it first. In either case, the
267224
/// key is moved to the "most recently used" position.
@@ -298,27 +255,14 @@ class ImageCache {
298255
final _CachedImage image = _cache.remove(key);
299256
if (image != null) {
300257
if (!kReleaseMode) {
301-
timelineTask.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
258+
timelineTask.finish(arguments: <String, dynamic>{'result': 'completed'});
302259
}
303-
// The image might have been keptAlive but had no listeners. We should
304-
// track it as live again until it has no listeners again.
305-
_trackLiveImage(key, image);
306260
_cache[key] = image;
307261
return image.completer;
308262
}
309263

310-
final _CachedImage liveImage = _liveImages[key];
311-
if (liveImage != null) {
312-
_touch(key, liveImage, timelineTask);
313-
if (!kReleaseMode) {
314-
timelineTask.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
315-
}
316-
return liveImage.completer;
317-
}
318-
319264
try {
320265
result = loader();
321-
_trackLiveImage(key, _CachedImage(result, null));
322266
} catch (error, stackTrace) {
323267
if (!kReleaseMode) {
324268
timelineTask.finish(arguments: <String, dynamic>{
@@ -338,37 +282,21 @@ class ImageCache {
338282
if (!kReleaseMode) {
339283
listenerTask = TimelineTask(parent: timelineTask)..start('listener');
340284
}
341-
// If we're doing tracing, we need to make sure that we don't try to finish
342-
// the trace entry multiple times if we get re-entrant calls from a multi-
343-
// frame provider here.
344285
bool listenedOnce = false;
345-
346-
// We shouldn't use the _pendingImages map if the cache is disabled, but we
347-
// will have to listen to the image at least once so we don't leak it in
348-
// the live image tracking.
349-
// If the cache is disabled, this variable will be set.
350-
_PendingImage untrackedPendingImage;
351286
void listener(ImageInfo info, bool syncCall) {
352287
// Images that fail to load don't contribute to cache size.
353288
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
354-
355289
final _CachedImage image = _CachedImage(result, imageSize);
356-
if (!_liveImages.containsKey(key)) {
357-
assert(syncCall);
358-
result.addOnLastListenerRemovedCallback(() {
359-
_liveImages.remove(key);
360-
});
361-
}
362-
_liveImages[key] = image;
363-
final _PendingImage pendingImage = untrackedPendingImage ?? _pendingImages.remove(key);
290+
final _PendingImage pendingImage = _pendingImages.remove(key);
364291
if (pendingImage != null) {
365292
pendingImage.removeListener();
366293
}
367-
// Only touch if the cache was enabled when resolve was initially called.
368-
if (untrackedPendingImage == null) {
369-
_touch(key, image, listenerTask);
370-
}
371294

295+
if (imageSize <= maximumSizeBytes) {
296+
_currentSizeBytes += imageSize;
297+
_cache[key] = image;
298+
_checkCacheSize(listenerTask);
299+
}
372300
if (!kReleaseMode && !listenedOnce) {
373301
listenerTask.finish(arguments: <String, dynamic>{
374302
'syncCall': syncCall,
@@ -381,58 +309,20 @@ class ImageCache {
381309
}
382310
listenedOnce = true;
383311
}
384-
385-
final ImageStreamListener streamListener = ImageStreamListener(listener);
386312
if (maximumSize > 0 && maximumSizeBytes > 0) {
313+
final ImageStreamListener streamListener = ImageStreamListener(listener);
387314
_pendingImages[key] = _PendingImage(result, streamListener);
388-
} else {
389-
untrackedPendingImage = _PendingImage(result, streamListener);
315+
// Listener is removed in [_PendingImage.removeListener].
316+
result.addListener(streamListener);
390317
}
391-
// Listener is removed in [_PendingImage.removeListener].
392-
result.addListener(streamListener);
393-
394318
return result;
395319
}
396320

397-
/// The [ImageCacheStatus] information for the given `key`.
398-
ImageCacheStatus statusForKey(Object key) {
399-
return ImageCacheStatus._(
400-
pending: _pendingImages.containsKey(key),
401-
keepAlive: _cache.containsKey(key),
402-
live: _liveImages.containsKey(key),
403-
);
404-
}
405-
406321
/// Returns whether this `key` has been previously added by [putIfAbsent].
407322
bool containsKey(Object key) {
408323
return _pendingImages[key] != null || _cache[key] != null;
409324
}
410325

411-
/// The number of live images being held by the [ImageCache].
412-
///
413-
/// Compare with [ImageCache.currentSize] for keepAlive images.
414-
int get liveImageCount => _liveImages.length;
415-
416-
/// The number of images being tracked as pending in the [ImageCache].
417-
///
418-
/// Compare with [ImageCache.currentSize] for keepAlive images.
419-
int get pendingImageCount => _pendingImages.length;
420-
421-
/// Clears any live references to images in this cache.
422-
///
423-
/// An image is considered live if its [ImageStreamCompleter] has never hit
424-
/// zero listeners after adding at least one listener. The
425-
/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] is used to
426-
/// determine when this has happened.
427-
///
428-
/// This is called after a hot reload to evict any stale references to image
429-
/// data for assets that have changed. Calling this method does not relieve
430-
/// memory pressure, since the live image caching only tracks image instances
431-
/// that are also being held by at least one other object.
432-
void clearLiveImages() {
433-
_liveImages.clear();
434-
}
435-
436326
// Remove images from the cache until both the length and bytes are below
437327
// maximum, or the cache is empty.
438328
void _checkCacheSize(TimelineTask timelineTask) {
@@ -464,73 +354,6 @@ class ImageCache {
464354
}
465355
}
466356

467-
/// Information about how the [ImageCache] is tracking an image.
468-
///
469-
/// A [pending] image is one that has not completed yet. It may also be tracked
470-
/// as [live] because something is listening to it.
471-
///
472-
/// A [keepAlive] image is being held in the cache, which uses Least Recently
473-
/// Used semantics to determine when to evict an image. These images are subject
474-
/// to eviction based on [ImageCache.maximumSizeBytes] and
475-
/// [ImageCache.maximumSize]. It may be [live], but not [pending].
476-
///
477-
/// A [live] image is being held until its [ImageStreamCompleter] has no more
478-
/// listeners. It may also be [pending] or [keepAlive].
479-
///
480-
/// An [untracked] image is not being cached.
481-
///
482-
/// To obtain an [ImageCacheStatus], use [ImageCache.statusForKey] or
483-
/// [ImageProvider.obtainCacheStatus].
484-
class ImageCacheStatus {
485-
const ImageCacheStatus._({
486-
this.pending = false,
487-
this.keepAlive = false,
488-
this.live = false,
489-
}) : assert(!pending || !keepAlive);
490-
491-
/// An image that has been submitted to [ImageCache.putIfAbsent], but
492-
/// not yet completed.
493-
final bool pending;
494-
495-
/// An image that has been submitted to [ImageCache.putIfAbsent], has
496-
/// completed, fits based on the sizing rules of the cache, and has not been
497-
/// evicted.
498-
///
499-
/// Such images will be kept alive even if [live] is false, as long
500-
/// as they have not been evicted from the cache based on its sizing rules.
501-
final bool keepAlive;
502-
503-
/// An image that has been submitted to [ImageCache.putIfAbsent] and has at
504-
/// least one listener on its [ImageStreamCompleter].
505-
///
506-
/// Such images may also be [keepAlive] if they fit in the cache based on its
507-
/// sizing rules. They may also be [pending] if they have not yet resolved.
508-
final bool live;
509-
510-
/// An image that is tracked in some way by the [ImageCache], whether
511-
/// [pending], [keepAlive], or [live].
512-
bool get tracked => pending || keepAlive || live;
513-
514-
/// An image that either has not been submitted to
515-
/// [ImageCache.putIfAbsent] or has otherwise been evicted from the
516-
/// [keepAlive] and [live] caches.
517-
bool get untracked => !pending && !keepAlive && !live;
518-
519-
@override
520-
bool operator ==(Object other) {
521-
if (other.runtimeType != runtimeType) {
522-
return false;
523-
}
524-
return other is ImageCacheStatus
525-
&& other.pending == pending
526-
&& other.keepAlive == keepAlive
527-
&& other.live == live;
528-
}
529-
530-
@override
531-
int get hashCode => hashValues(pending, keepAlive, live);
532-
}
533-
534357
class _CachedImage {
535358
_CachedImage(this.completer, this.sizeBytes);
536359

0 commit comments

Comments
 (0)