Images in listview are not released from memory when out of view
Asked Answered
B

4

9

I am displaying images from the internet in a vertical ListView. I fetch the images using http.get (not using cached network image cuz I do not want to cache the images). Then I insert the image Uint8List into image.memory(). What happens is that as the user scrolls the list and images are loading, the ram keeps increasing until the whole app crashes. Any ideas what to do ?

Binary answered 24/2, 2021 at 2:7 Comment(2)
Are you using ListView.builder() ?Amberly
Yes, I am using ListView.builder()Binary
B
10

Yeah, this is the normal behavior. I don't know why exactly. My theory is that the images by default are disposed if the dart objects holding references to them are garbage collected rather then when the widgets are knocked off the widgets tree, but don't take my word for it- that's just my personal reasoning. It may be completely wrong.

To get around this, I use Extended Image pakcage It's constructors take a bool clearMemoryCacheWhenDispose which disposes of images in RAM in scroll lists. You may do that or visit the package code to see how he's doing it, and replicate it.

Other advice I can give is to make sure your images are of the appropriate size. If you are loading your images locally check this part of the documentation to have different dimensions selected for different screen sizes https://flutter.dev/docs/development/ui/assets-and-images#loading-images

If you are fetching your images from network which is more likely, make sure their sizes are appropriate, and have different sizes served if you can. If you don't have control over that set cacheWidth and cacheHeight in Image.memory these will reduce the cached image in memory (RAM). You see by default Flutter caches images in full resolution despite their apparent size in the device/app. For example if you want your image to display in 200x200 box then set cacheWidth to 200 * window.devicePixelRatio.ceil() you will find window in dart:ui, and if you only set cacheWidth, the ratio of your images will remain true. Same true if you only set cacheHeight. Also do use ListView.builder as suggested.

I am disappointed at how little is said about this in Flutter docs. Most of the time people discover this problem when their apps start crashing. Do check your dev tools regularly for memory consumption. It's the best indicator out there.

Cheers and good luck

Bertabertasi answered 24/2, 2021 at 5:45 Comment(6)
Hi @moneer, I am using the extendedImage package but I am facing an issue, whenever I scroll away from the image and return, be it the image has loaded or not, it is rebuilt and starts fetching the image all over. Any suggestions to prevent the images from rebuilding whenever the user scrolls.Binary
If you set clearMemoryCacheWhenDispose the image will not be cached in RAM so when you scroll away and return it'll be re-fetched. You can't have it both ways @mariaMadBertabertasi
There is a way to control the number of images kept in RAM before they are disposed. I'm not sure how to better describe this, but if you want check out the cacheExtent parameter in ListView.builder it might help. With the above comment with clearMemoryCacheWhenDispose it will be cached but disposed as the name suggests :) There is another parameter called enableMemoryCache. Experiment with them and see what works best for you.Bertabertasi
If you want finer control over image cache then you are gonna have to figure out the cache API with ImageCache and come up with your own eviction strategy. Unfortunately I don't know the first thing about that :(Bertabertasi
One last thing, my images are fetched through a dynamic url. How can I cache the images in the extendedImage package. What I plan to do is the following: See whether the image is within the viewport, if yes, I fetch it from network or from cache if cached, once the image is out of view I dispose it and so on.Binary
There is boolean parameter on the package constructors called cache that'll cache images for you in local storage if that's what you mean. I have clearMemoryCacheWhenDispose set to true, and cache set to true so my images are cached in local storage, and are only fetched from network when they are not avaible in local storage, and when the widgets are disposed they are evicted from RAM. Check your dev tools to have a better picture of what's going on there is even a tap to monitor network traffic.Bertabertasi
D
4

I was having the same issue and found a fix thanks to @moneer!

Context:

Users in my app can create shared image galleries which can easily contain several hundred images. Those are all displayed in a SliverGrid widget. When users scrolled down the list, too many images were loaded into RAM and the app would crash.

Things I had already implemented:

  • Image resizing on the server side and getting the appropriate sized images on the client based on the device pixel ratio and the tile size in the gallery
  • I made sure that my image widgets were properly disposing when out of view, but the memory size kept building up as the user scrolled through all the images anyway
  • Implement cacheHeight to limit the size of the cached image to the exact size of the displayed image

All these things helped but the app would eventually still crash every time the user scrolled down far enough.

The fix:

After some googling I stumbled upon this thread and tried the extended_image_package as @moneer suggested. Setting the clearMemoryCacheWhenDispose flag to true fixed the issue of the app crashing as it was now properly clearing the images from memory when they were out of view. Hooray! However, in my app users can tap on an image and the app navigates to an image detail page with a nice Hero animation. When navigating back the image would rebuild and this would cause a rebuild 'flicker'. Not that nice to look at and kind of distracting.

I then noticed that there's also an enableMemoryCache flag. I tried setting this to false and that seems to work nicely for me. The Network tab in Dart DevTools seems to show that all images are only fetched from the network once when scrolling up and down the gallery multiple times. The app does not crash anymore.

I'll have to more testing to see if this leads to any performance issues (if you can think of any please let me know).

The final code:

ExtendedImage.network(
  _imageUrl,
  cacheHeight: _tileDimension,
  fit: BoxFit.cover,
  cache: true, // store in cache
  enableMemoryCache: false, // do not store in memory
  enableLoadState: false, // hide spinner
)
Doorjamb answered 13/3, 2022 at 8:54 Comment(0)
M
0

I had a similar issue when I loaded images from files in a ListView.

[left-side: old code, right-side: new code]

The first huge improvement for me: not to load the whole list at once

  • ListView(children:[...]) ---> ListView.builder(...).

The second improvement was that images are no longer loaded at full-size:

  • Image.file("/path") ---> Image.file("/path", cacheWidth: X, cacheHeight: Y)

These two things solved my memory problems completely

Mutt answered 24/2, 2022 at 9:3 Comment(0)
G
0

Ideally caching happens kind of by default after some conditions are fulfilled. So its upon your app to be responsible to handle and control how the caching will happen. Checkout this answer https://mcmap.net/q/940621/-how-android-http-cache-is-working

Gas answered 27/2, 2022 at 4:40 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.