33// found in the LICENSE file.
44
55import 'dart:developer' ;
6- import 'dart:ui' show hashValues;
76
87import '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
7871class 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-
534357class _CachedImage {
535358 _CachedImage (this .completer, this .sizeBytes);
536359
0 commit comments