Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Live image cache #50318

Merged
merged 26 commits into from
Feb 14, 2020
Merged

Live image cache #50318

merged 26 commits into from
Feb 14, 2020

Conversation

dnfield
Copy link
Contributor

@dnfield dnfield commented Feb 7, 2020

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:

  • Assert that the cache can weakly hold an image even if it's otherwise too big to fit in the cache or the cache is disabled
  • Assert that the cache does not weakly hold an image that has no listeners.
  • Test that an ImageProvider can handle errors when trying to find its cache location
  • Test that an ImageProvider can find its cache location
  • Test that precacheImage weakly holds an image reference for a frame (but no longer)
  • Test that this is enough time for someone to grab the stream and hold the image in memory even if it is not in strongly in the cache.

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.

  • I read the Contributor Guide and followed the process outlined there for submitting PRs.
  • I signed the CLA.
  • I read and followed the Flutter Style Guide, including Features we expect every widget to implement.
  • I read the Tree Hygiene wiki page, which explains my responsibilities.
  • I updated/added relevant documentation (doc comments with ///).
  • All existing and new tests are passing.
  • The analyzer (flutter analyze --flutter-repo) does not report any problems on my PR.
  • I am willing to follow-up on review comments in a timely manner.

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.

  • No, no existing tests failed, so this is not a breaking change.
  • Yes, this is a breaking change. If not, delete the remainder of this section.
    • I wrote a design doc: https://flutter.dev/go/template Replace this with a link to your design doc's short link
    • I got input from the developer relations team, specifically from: Replace with the names of who gave advice
    • I wrote a migration guide: Replace with a link to your migration guide

@fluttergithubbot fluttergithubbot added framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels. labels Feb 7, 2020
@dnfield
Copy link
Contributor Author

dnfield commented Feb 7, 2020

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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to land in #50316

///
/// To obtain an [ImageCacheLocation], use [FlutterImageCache.locationForKey] or
/// [ImageProvider.findCacheLocation].
class ImageCacheLocation {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/cc @jamesderlin who was looking for a way to know if an image was pending in the cache.

Comment on lines 173 to 181
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);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

_handleImageStreamCompleterCreated per style guide

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rather than saying it shouldn't be called, say when it should be called.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

/// 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 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be schedulePostFrameCallback so that you don't actually schedule a frame

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, and updated tests relevant to this to actually schedule a frame where needed.

@@ -65,9 +65,11 @@ mixin PaintingBinding on BindingBase, ServicesBinding {

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please avoid the word "weak" throughout

@dnfield dnfield changed the title Weak image cache Live image cache Feb 11, 2020
@dnfield
Copy link
Contributor Author

dnfield commented Feb 14, 2020

I've merged in the image tracing tests. This should be fully ready for review and landing on approval.

@dnfield
Copy link
Contributor Author

dnfield commented Feb 14, 2020

(Fixed some bugs in my last push - I had forgotten to delete some code and had a bad assert in there)

/// Returns whether this `key` has been previously added by [putIfAbsent].
bool containsKey(Object key) {
return _pendingImages[key] != null || _cache[key] != null;
}

/// The number of live images being held by the [ImageCache].
///
/// Compare with [ImageCache.currentSize] for completed held images.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what "completed held images" is?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
///
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove one blank line.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

};


// If an error is added to a synchronous completer before a listener has been
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@dnfield
Copy link
Contributor Author

dnfield commented Feb 14, 2020

Ahh I got mixed up about the dangerzone stuff - I thought that one was on ImageCache. I'll refactor.

Copy link
Member

@goderbauer goderbauer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@dnfield dnfield merged commit 1602be6 into flutter:master Feb 14, 2020
@dnfield dnfield deleted the weak_image_cache branch February 14, 2020 23:03
@dnfield
Copy link
Contributor Author

dnfield commented Feb 15, 2020

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.

@dnfield
Copy link
Contributor Author

dnfield commented Feb 15, 2020

And if anyone would really like to see a migration guide for this, I can certainly write one :)

@dnfield dnfield added the a: images Loading, displaying, rendering images label Feb 18, 2020
dnfield added a commit that referenced this pull request Feb 20, 2020
dnfield added a commit that referenced this pull request Feb 20, 2020
dnfield added a commit to dnfield/flutter that referenced this pull request Feb 22, 2020
@dnfield dnfield mentioned this pull request Feb 22, 2020
dnfield added a commit that referenced this pull request Feb 24, 2020
* Revert "Revert "Live image cache (#50318)" (#51131)"

This reverts commit 2f09d60.

* Fix eviction of a pending image
@lifelikejuly
Copy link

Please I want to know version1.17.0 ImageCache add liveImage how to useful。

@dnfield
Copy link
Contributor Author

dnfield commented May 11, 2020

It is automatic

@lifelikejuly
Copy link

It is automatic

Sorry, actually I mean want to ask what it does

@dnfield
Copy link
Contributor Author

dnfield commented May 11, 2020

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.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jul 31, 2021
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
a: images Loading, displaying, rendering images c: API break Backwards-incompatible API changes framework flutter/packages/flutter repository. See also f: labels. team Infra upgrades, team productivity, code health, technical debt. See also team: labels.
Projects
None yet
8 participants