-
Notifications
You must be signed in to change notification settings - Fork 26.9k
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
Image.network's caching strategy is problematic #47378
Comments
Is the gist of this bug that we should make it configurable wether the encoded or decoded image is cached? Would that be something you want to configure on a per-image or per-app base? |
@goderbauer - yes. I don't have full details worked out, but didn't want to lose track of this. |
I suggest we create an on-disk caching mechanism for encoded network images, something akin to what web browsers do. |
I think that overall, the issue with Image.network is that it is too much like |
Maybe we could come up with some kind of inherited widget that deals with caching decisions for images. That could also help with the sliver list case - a user could choose to exclude that subtree from image caching. It could also help with network images where the caching widget could cache the encoded bytes instead of the raw. |
I think for this, rather than something specific for images, it would be good to have a widget that can handle caching decisions for network requests. I'm imagining an inherited widget that would be looked for by other widgets that make network calls, and if it's there they're expected to check if it has cached response data first. Whether to cache the image would then be a separate decision, and possibly handled as in the situation described in #47380 (comment) |
I'm all for solving the generic case, however I would suggest to focus, understand and ideally improve the image-related issues first. Hopefully we'll learn something along the way :). IMHO there are two mostly orthogonal issues:
Let me expand a little more on #2, since I believe this to be the more fundamental issue. Today, the ImageProvider itself is "stateless". ImageProvider::resolve() depends on the cache to avoid redundant resolutions. In practice, this means, for example:
If I had to guess, the image provider being stateless might have been the reason for growing the size limits of a limited cache in the first place because not-caching really isn't an option. My suggestion would be to:
Curious to hear your thoughts. |
@ignatz - I'm not entirely sure how making it stateful would help things. Can you elaborate on that? What would you expect the behavior of One of the problems I see with NetworkImage in particular is that we're assuming that you want to hit the network again any time you drop the image from your cache. Another problem I see is that we assume there can be one global static ImageCache that all providers interact with in the same way rather than image caches specific to specific parts of the tree (perhaps this is what you're getting at by state/stateless, I'm not sure - but I don't think we need to make providers stateful to fix that). |
As a developer when calling precacheImage I would expect that by the time I commit to rendering the Image I can either join into the ongoing fetch or am guaranteed to not fetch again whenever the fetch has completed. It's not clear to me why re-fetching would be intended behavior. Let me make one more example based a concrete problem I've encountered in our code-base. We're using the From an image or image provider API point of view it's hard to reason about the behavior, since the cache is rather internal and the semantics of Personally, I would find it more intuitive if the ImageProvider has a lifecycle and cycles from referencing the url to referencing the encoded image or even the decoded image? (If there are memory concerns the provider could still have explicit lifecycle methods to e.g. reset it into an earlier lifecycle stage) |
I think we could get what you're looking for by distinguishing between how the encoded bytes are loaded and how the image is actually decoded, right? For example, if there was some layer responsible for loading the network bytes separate from a layer responsible for decoding. The whole point of ImageProvider is to manage interaction with the cache during image decoding AFAICT. Your case sounds like you'd rather not use ImageProvider at all, and instead handle making the dart:ui calls to decode the image yourself and eventually create widgets once that's done. |
I'm not sure. Are you suggesting a multi-layer caching approach?
Today we get two calls to
Something should likely be stateful, it could be the network ImageProvider, it could be something else .
Interesting, I hadn't thought of ImageProvider as an API to merely interact with the cache. FWIW, having the image provider "own" the downloaded image would also help with, e.g.
Generally, I'm sure we could change the APIs, my suggestions were mostly targeted towards making the existing APIs work even if the underlying cache behavior changes, e.g. disabled, don't cache large images, clear the cache, ... |
@dnfield This is what my memory consumption looks like. The highlighted section is the normal app use. Doing some API calls with Dio. After the highlighted section the app displays 20 network images. With the size of a couple kb. This is done on a iPhone 7 plus. But we are having this issue on all our test devices. android & iOS This triggers crashes after some use of the app. Because the app spikes again once new pictures are reloaded |
@TahaTesser any idea why this is happening? main.dart example:
pubspec.yaml
|
Marking this as P5 - I think this problem is better solved in some community packages, but we could still ake it better in the framework. |
Images stay in memory until they're evicted from the There's no time based component for images, although it might be interesting to clean up images that haven't been touched in the last N seconds. |
But aren't they evicted from ImageCache near-instantly when there is no listeners? In practice this very aggressive, and leads to something re-loading when you switch pages, even though you may have looked at it 5 seconds ago. My ask is basically to add a time component, so listener count is not the only deciding factor. But maybe I mis-understand, I can't find a clear explenation of ImageCache's evictiona rationale, but the behavior I'm seeing in-app seems to reflect this. Seems like the time-based method would work most elegantly with Flutters lists that rely so much on virtualization to be performant. Here's an example of the type of UI that is coming to mind: It just works of the rationale that if you viewed something recently, there's a high change you will view it again momentarily, and just optimizing for that high % use case, and also the fact that we are on very powerful machines, and if we can use the RAM to improve the performance, we should. Trying to build something like this, where you just want smooth scrolling, and no visible loading on the images, just feels like you're fighting the framework the entire time, as it's making these sorta mobile-centric opinionated decisions about resource management. |
They are not evicted from the image cache until something else needs to take their place, and then yes they are evicted immediately. |
I suppose I need to up my cache limits then! [Edit] Works perfectly now, sorry for the noise. |
Currently,
Image.network
will cache the decoded version of the image in theImageCache
under the following circumstances:One problem we have is that if the network image is > cache size, we automatically resize it to make the cache bigger and try again. @gaaclarke has been working on fixing that.
Another problem is that we only provide ways to cache the decoded image, which may be much much larger than the encoded data. A developer may want to cache the encoded bytes to avoid a network call, but not cache the decoded image (which might be very large). We should make it easier to do the right thing here.
/cc @gaaclarke @goderbauer @jonahwilliams @liyuqian @zmtzawqlp @cbracken
The text was updated successfully, but these errors were encountered: