Repository navigation
Live image cache - #50318
Live image cache#50318
Conversation
|
Argh. I meant to open this as a draft. Oh well. |
| expect(imageStreamCompleter.listeners.length, 0); | ||
| // Make sure the second listener can be called re-entrantly. | ||
| listeners[0].onImage(null, null); | ||
| listeners[0].onImage(null, null); |
| /// | ||
| /// To obtain an [ImageCacheLocation], use [FlutterImageCache.locationForKey] or | ||
| /// [ImageProvider.findCacheLocation]. | ||
| class ImageCacheLocation { |
There was a problem hiding this comment.
/cc @jamesderlin who was looking for a way to know if an image was pending in the cache.
d953dda to
257ef91
Compare
| final FlutterImageCache flutterImageCache = imageCache as FlutterImageCache; | ||
| expect(flutterImageCache.locationForKey(testImage).pending, true); | ||
| expect(flutterImageCache.locationForKey(testImage).weak, true); | ||
| flutterImageCache.clear(); | ||
| expect(flutterImageCache.locationForKey(testImage).pending, false); | ||
| expect(flutterImageCache.locationForKey(testImage).weak, true); | ||
| flutterImageCache.clearWeakImages(); | ||
| expect(flutterImageCache.locationForKey(testImage).pending, false); | ||
| expect(flutterImageCache.locationForKey(testImage).weak, false); |
There was a problem hiding this comment.
clear does remove the pending image, but this test no longer captures how to check if an image is pending or not.
This test could be independently rewritten without these changes to use the existing containsKey API, although the new API offers more robust ways to check if an image is pending.
| /// This method exists so that [FlutterImageCache] can add to its weak cache | ||
| /// once an [ImageStreamCompleter] has been created by this cache. | ||
| @protected | ||
| void _onImageStreamCompleterCreated(Object key, ImageStreamCompleter completer) {} |
There was a problem hiding this comment.
_handleImageStreamCompleterCreated per style guide
There was a problem hiding this comment.
Removing this since we're just going to break the API
|
|
||
| /// Clears any weak references to images in this cache. | ||
| /// | ||
| /// This should not ordinarily be called by applications. |
There was a problem hiding this comment.
Rather than saying it shouldn't be called, say when it should be called.
| /// This cache also exposes internal dimensions of the implementation, such as | ||
| /// checking whether an image for a given key is pending, weak, or completed. | ||
| /// See [ImageCacheLocation] for more details. | ||
| class FlutterImageCache extends ImageCache { |
There was a problem hiding this comment.
in the api design review meeting we discussed just merging FlutterImageCache into ImageCache, and make the test-only features be methods called debugFoo... with their bodies in asserts.
There was a problem hiding this comment.
I'm leaning towards making these non-debug/assert methods at this point - this could be valuable information at runtime (e.g. if an application wants to know how many live images are on the screen before decoding more). I'm also documenting [precacheImage] as relying on this behavior now.
| stream.removeListener(listener); | ||
| // Give callers at least one frame to subscribe to the image stream | ||
| // so that FlutterImageCache can weakly hold it without losing it. | ||
| SchedulerBinding.instance.scheduleFrameCallback((Duration timeStamp) { |
There was a problem hiding this comment.
should probably be schedulePostFrameCallback so that you don't actually schedule a frame
There was a problem hiding this comment.
Done, and updated tests relevant to this to actually schedule a frame where needed.
| @@ -65,9 +65,11 @@ mixin PaintingBinding on BindingBase, ServicesBinding { | |||
|
|
|||
There was a problem hiding this comment.
please avoid the word "weak" throughout
| /// the network if they are referenced in the [putIfAbsent] method), but the raw | ||
| /// bits are kept in memory for as long as the application is using them. | ||
| /// | ||
| /// The cache also holds a list of "live" references. An image is considered |
There was a problem hiding this comment.
nit: the paragraph above says that active images may also get removed from the cache, this paragraph talks about live images. Maybe specify in the above paragraph when "active" images can get evicted (e.g. when they no longer are "live").
There was a problem hiding this comment.
Also: It's probably not super clear what the difference between "actively in use" and live is...
There was a problem hiding this comment.
I'm removing the above paragraph - thanks for catching that.
| return image.completer; | ||
| } | ||
|
|
||
| result = _liveImages[key]; |
There was a problem hiding this comment.
Shouldn't these go back into the regular cache (if they fit) if they have been used again?
There was a problem hiding this comment.
Ah, yes. I'll fix this.
| class ImageCache { | ||
| final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{}; | ||
| final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{}; | ||
| final Map<Object, ImageStreamCompleter> _liveImages = <Object, ImageStreamCompleter>{}; |
There was a problem hiding this comment.
For future readers: Maybe add a comment here what "live" means?
| } | ||
|
|
||
| /// The [ImageCacheLocation] information for the given `key`. | ||
| ImageCacheLocation locationForKey(Object key) => ImageCacheLocation._( |
There was a problem hiding this comment.
nit: per style guide, use => only if everything fits on one line.
There was a problem hiding this comment.
Done. I had this on one line, realized it was too long, and then forgot to change the fat arrow :)
| return _pendingImages[key] != null || _cache[key] != null; | ||
| } | ||
|
|
||
| /// The number of live images being held by the [ImageCache]. Compare with |
There was a problem hiding this comment.
nit: first paragraph should be one sentence summary.
| // hence the need for the `didError` flag. | ||
| final Zone dangerZone = Zone.current.fork( | ||
| specification: ZoneSpecification( | ||
| handleUncaughtError: (Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) { |
|
|
||
| final List<VoidCallback> _onLastListenerRemovedCallbacks = <VoidCallback>[]; | ||
|
|
||
| /// A adds a callback to call when [removeListener] results in an empty |
| /// A adds a callback to call when [removeListener] results in an empty | ||
| /// list of listeners. | ||
| /// | ||
| /// This callback will never fire if [removeListener] is never called. |
There was a problem hiding this comment.
Feels odd that there's no way to remove a onlastlistenerremoved listener.
There was a problem hiding this comment.
I don't have a use case for it. I also suspect that it would be better to track these by ID if we ever want to do that, to avoid the confusion about what happens when someone removes a duplicate listener. WDYT?
| /// as long as both images share the same key, and the image is held by the | ||
| /// cache. | ||
| /// | ||
| /// The cache may refuse to hold the image it is disabled, the image is too |
There was a problem hiding this comment.
Is this sentence missing an "if" or something?
| /// as long as both images share the same key, and the image is held by the | ||
| /// cache. | ||
| /// | ||
| /// The cache may refuse to hold the image it is disabled, the image is too |
There was a problem hiding this comment.
Also: there are two toos (one at the end of this line and one in the next line)
|
I've merged in the image tracing tests. This should be fully ready for review and landing on approval. |
611b5a8 to
d60d64e
Compare
|
(Fixed some bugs in my last push - I had forgotten to delete some code and had a bad assert in there) |
|
|
||
| /// The number of live images being held by the [ImageCache]. | ||
| /// | ||
| /// Compare with [ImageCache.currentSize] for completed held images. |
There was a problem hiding this comment.
Not sure what "completed held images" is?
There was a problem hiding this comment.
Missed a text replacement. done.
| /// | ||
| /// A [pending] image is one that has not completed yet. It may also be tracked | ||
| /// as [live] because something is listening to it. | ||
| /// |
| }; | ||
|
|
||
|
|
||
| // If an error is added to a synchronous completer before a listener has been |
There was a problem hiding this comment.
Wait, isn't the other occurence also in this file? Could this be just be refactored into a private method? I don't think there's any danger of anybody accessing a private method from here?
| return; | ||
| } | ||
| _liveImages[key] = image; | ||
| image.completer.addOnLastListenerRemovedCallback(() { |
There was a problem hiding this comment.
What if nobody ever listens to the stream (and hence this callback never fires)? It appears that we'd never remove the image from the live cache?
|
Ahh I got mixed up about the dangerzone stuff - I thought that one was on ImageCache. I'll refactor. |
|
Quick update on this: When I opened the PR, it was a breaking change. During the course of work on this PR, the tests it broke no longer were breaking (in fact, they disappeared entirely!). On landing it, it no longer breaks our compatibility guidelines (https://flutter.dev/docs/resources/compatibility). I'll still send an email to Flutter-announce explaining this. |
|
And if anyone would really like to see a migration guide for this, I can certainly write one :) |
This reverts commit 2f09d60.
|
Please I want to know version1.17.0 ImageCache add liveImage how to useful。 |
|
It is automatic |
Sorry, actually I mean want to ask what it does |
|
The design doc and linked PRs have more details, but in a nutshell this makes sure that once you resolve an image, it continues to be reachable as long as its listener count has not dropped to zero, even if it did not otherwise fit in the image cache. This is important especially for large images or cache configurations that do not allow for many images. |
WIP - this should not land until #50316; design doc for this is at https://flutter.dev/go/widget-tree-image-cache, although the approach has changed a bit since writing the doc. /cc @goderbauer @ds84182 @liyuqian @gaaclarke @ignatz @alml
Description
Related Issues
fixes #48731
fixes #49456
fixes #45406
Tests
I added the following tests:
Checklist
Before you create this PR confirm that it meets all requirements listed below by checking the relevant checkboxes (
[x]). This will ensure a smooth and quick review process.///).flutter analyze --flutter-repo) does not report any problems on my PR.Breaking Change
Did any tests fail when you ran them? Please read Handling breaking changes.
I don't believe this is a breaking change, but once this is cleaned up a bit I'll manually run it through google's internal test suites.