1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142
| class ImageCache { ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) { TimelineTask? timelineTask; TimelineTask? listenerTask; ImageStreamCompleter? result = _pendingImages[key]?.completer; if (result != null) { return result; } final _CachedImage? image = _cache.remove(key); if (image != null) { if (!kReleaseMode) { timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'}); } _trackLiveImage( key, image.completer, image.sizeBytes, ); _cache[key] = image; return image.completer; }
final _LiveImage? liveImage = _liveImages[key]; if (liveImage != null) { _touch( key, _CachedImage( liveImage.completer, sizeBytes: liveImage.sizeBytes, ), timelineTask, ); if (!kReleaseMode) { timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'}); } return liveImage.completer; }
try { result = loader(); _trackLiveImage(key, result, null); } catch (error, stackTrace) { if (onError != null) { onError(error, stackTrace); return null; } else { rethrow; } }
if (!kReleaseMode) { listenerTask = TimelineTask(parent: timelineTask)..start('listener'); } bool listenedOnce = false;
final bool trackPendingImage = maximumSize > 0 && maximumSizeBytes > 0; late _PendingImage pendingImage;
void listener(ImageInfo? info, bool syncCall) { int? sizeBytes; if (info != null) { sizeBytes = info.sizeBytes; info.dispose(); } final _CachedImage image = _CachedImage( result!, sizeBytes: sizeBytes, );
_trackLiveImage(key, result, sizeBytes);
if (trackPendingImage) { _touch(key, image, listenerTask); } else { image.dispose(); }
_pendingImages.remove(key); if (!listenedOnce) { pendingImage.removeListener(); } if (!kReleaseMode && !listenedOnce) { listenerTask!.finish(arguments: <String, dynamic>{ 'syncCall': syncCall, 'sizeInBytes': sizeBytes, }); timelineTask!.finish(arguments: <String, dynamic>{ 'currentSizeBytes': currentSizeBytes, 'currentSize': currentSize, }); } listenedOnce = true; }
final ImageStreamListener streamListener = ImageStreamListener(listener); pendingImage = _PendingImage(result, streamListener); if (trackPendingImage) { _pendingImages[key] = pendingImage; } result.addListener(streamListener);
return result; }
void _touch(Object key, _CachedImage image, TimelineTask? timelineTask) { if (image.sizeBytes != null && image.sizeBytes! <= maximumSizeBytes && maximumSize > 0) { _currentSizeBytes += image.sizeBytes!; _cache[key] = image; _checkCacheSize(timelineTask); } else { image.dispose(); } }
void _trackLiveImage(Object key, ImageStreamCompleter completer, int? sizeBytes) { _liveImages.putIfAbsent(key, () { return _LiveImage( completer, () { _liveImages.remove(key); }, ); }).sizeBytes ??= sizeBytes; }
void _checkCacheSize(TimelineTask? timelineTask) { final Map<String, dynamic> finishArgs = <String, dynamic>{}; TimelineTask? checkCacheTask; while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) { final Object key = _cache.keys.first; final _CachedImage image = _cache[key]!; _currentSizeBytes -= image.sizeBytes!; image.dispose(); _cache.remove(key); } }
}
|