Flutter_cached_network_image: perf: memory Memory Overflow Issue due to CachedNetworkImage

Created on 17 Jul 2020  路  29Comments  路  Source: Baseflow/flutter_cached_network_image

Hi,
Allez gezellig! Thanks for creating this plugin. I was really concerned about my app crashes and then I did memory profiling on the app. And I am attaching the screenshots of heap dumps here. I have used Cached Network Image in my entire project and have not used Network. Image or any other plugin. As you can see that the images/ Cached Network Image is responsible for app crashes and out of memory errors. I have also read the similar issues here in issues section for the plugin. And now I am reading even Network.Image or ImagePicker causes the same kind of behavior if used without cacheWidth/width and cacheHeight. I think flutter team should update their official docs for these plugins/libraries so that unexpected behavior is not triggered causing the production app to crash if developers used these plugins without any recommendations to reduce memory consumption in the heap.
It's also important to mention in Readme for this plugin to use all recommended measures such as memWidth, memHeight or use the 2.3.0 beta version so that future developers can use this plugin effectively to add multiple images in the app. It would be also helpful if you can elaborate on all these recommended measures here or update the Readme so that it can help the future developers.
I also tried using 2.3.0-beta.1 in android app because that't the latest version on stable flutter channel. I also tried adding memWidth and memHeight parameters to CachedNetworkImage. But to no avail. That's why need some recommendations from the contributors. I saw the code for CachedNetwork image too and it's using default cache manager from flutter cache manager package if not provided with cache manager as parameter. The only thing that now I could try is to limit the maximum number of cache objects for Cache Manager which is passed as parameter to the Cached Network Image.
So my question to the team are as follows:

  1. What's the maximum number of cache objects that default cache manager can hold because in my code, I have seen it increasing to 600-700 object levels in heap dump which would definitely crash the app frequently.
    While looking at the code for default cache manager in this link https://github.com/Baseflow/flutter_cache_manager/blob/develop/flutter_cache_manager/lib/src/cache_manager.dart
    I observed that max duration is set to 30 days and max number of objects is set at 200 for Default Cache Manager. As I am using Cached network image in number of places in my code so that's why it might be going to 600-700 level. Is it recommend to use Cache Manager as global variable in the code and then pass that global Cache Manager as parameter to different instances of Cached Network Image?
  2. And what should be the ideal number of cache objects we should limit the cache manager to, if that global cache manager is passed as parameter to Cached Network Image.
    Looking forward to the reply.
    Dank je wel!

snapshot1
snapshot2
snapshot3
snapshot7
snapshot8
snapshot9
snapshot10
snapshot11

Most helpful comment

I am also experiencing extreme memory issues.

I prefer this library because it has fade in animations and provides good functionality,
but it can easily bring my app to use over 1GB of RAM and beyond.

I have experienced the app using so much RAM that the OS starts killing off all background apps to eventually kill off my app in a last attempt to not die, because images are not freed. External memory just goes on to consume all available RAM.

My app allows accessing large images up to 8k etc and if the user loads a significant amount of them,
this will lead to the app getting killed by the OS or like others mentioned, just crashing.

I understand that keeping images in RAM is important so they dont have to be loaded again and again and this is also very useful in the case of my app, but that there is literally no limit to RAM consumption is rather detrimental.

This is a rather big issue for me right now, and I would love it if there was a fix.

All 29 comments

Important update: I also tried using global cache manager with maxNumberOfObjects limited to some number which could be stored in cache. Then I shared this BaseCacheManager instance as parameter to all instances of CachedNetworkImage in my code as shown as follows:

class ImageCacheManager extends BaseCacheManager {
static const key = "MyApp_Cache"; //just use some unique name instead of MyApp
static ImageCacheManager _instance;
factory ImageCacheManager() {
if (_instance == null) {
_instance = new ImageCacheManager._();
}
return _instance;
}
ImageCacheManager._() : super(key,
maxAgeCacheObject: Duration(days: 1),
maxNrOfCacheObjects:20
);
Future getFilePath() async {
var directory = await getTemporaryDirectory();
return path.join(directory.path, key);

}
}
ImageCacheManager ImageCacheStorage=new ImageCacheManager();

CachedNetworkImage(
imageUrl: "",
placeholder: (context, url) =>
Image.memory(kTransparentImage),
errorWidget: (context, url, error) =>
Image.memory(kTransparentImage),
fadeInCurve: Curves.easeIn,
memCacheWidth: 500,
memCacheHeight:500,
cacheManager: ImageCacheStorage,
)
But still the memory used in the heap is increasing to 500MB-600MB which doesn't make any sense to me if I am limiting the maxNumber of objects stored in cache to 20.

Then I tried using Image.network with cacheWidth and cacheHeight parameters and setting imageCache size to 50MB as follows:
imageCache.maximumSizeBytes=50000000;
Network Image is behaving in correct way and heap size doesn't increase beyond 70-80 MB.

For now, I would prefer to use NetworkImage to avoid app crashes even if CachedNetworkImage has positive points such as errorWidget and placeholderWidget.
I hope that the contributors could comment on the issue for how to use the CachedNetworkImage effectively in case of large number of images in the app without increasing the heap size to a large amount so that it can help me and other future developers to take the right decision :)

@pranavkpr1 _imageCache.clearLiveImages(); Can reduce a lot of memory
鎴睆2020-08-08 涓嬪崍4 22 39

@pranavkpr1 _imageCache.clearLiveImages(); Can reduce a lot of memory
鎴睆2020-08-08 涓嬪崍4 22 39

Hi @pushuhengyang , Thanks for the solution, but can you please help us where to use this code. We are also using cachednetwork image and facing the OOM in iOS (Low-end devices) mostly.

I am also experiencing extreme memory issues.

I prefer this library because it has fade in animations and provides good functionality,
but it can easily bring my app to use over 1GB of RAM and beyond.

I have experienced the app using so much RAM that the OS starts killing off all background apps to eventually kill off my app in a last attempt to not die, because images are not freed. External memory just goes on to consume all available RAM.

My app allows accessing large images up to 8k etc and if the user loads a significant amount of them,
this will lead to the app getting killed by the OS or like others mentioned, just crashing.

I understand that keeping images in RAM is important so they dont have to be loaded again and again and this is also very useful in the case of my app, but that there is literally no limit to RAM consumption is rather detrimental.

This is a rather big issue for me right now, and I would love it if there was a fix.

@pranavkpr1 _imageCache.clearLiveImages(); Can reduce a lot of memory
鎴睆2020-08-08 涓嬪崍4 22 39

Hi @pushuhengyang , Thanks for the solution, but can you please help us where to use this code. We are also using cachednetwork image and facing the OOM in iOS (Low-end devices) mostly.

Judge every time you load the image
鎴睆2020-08-24 涓嬪崍7 14 18

@pushuhengyang and where would you call _checkMemory()?
Can you provide use with a minimal working example?

@pushuhengyang and where would you call _checkMemory()?
Can you provide use with a minimal working example?

鎴睆2020-08-27 涓嬪崍6 03 19
It's best to encapsulate a widget

Hi all.
In version 2.3.1 there have been some improvements in memory usage. Next to that you can set the height and/or width of the image in memory using memCacheWidth/memCacheHeight. Setting these to a reasonable size should help a large part of the memory issues.
https://pub.dev/documentation/cached_network_image/latest/cached_network_image/CachedNetworkImage-class.html

Hi Rene,
As I mentioned in this post, I have already tried using dev version of CachedNetworkImage which is the new version you are asking to try. I have already tried adding memCacheWidth/ memCacheHeight as mentioned in the original post but still I was facing the same memory issue when I was performing memory profiling as shown in the attached screenshots in the original post. According to my experience, we can't use this package in e-commerce or social media kind of app where there is use case of showing large number of images as it can trigger crashes due to OOM issue. Hope to hear from you soon.

Sorry didn't read it good enough. I have to take some time to dig into this.

I am also facing the same issue. On the simulator, it just works and on the real iPhone, it crashes.

For the time being, I switched to https://github.com/humblerookie/optimized_cached_image which is very similar to flutter_cached_network_image and in fact, internally uses octo_image.

One more thing I noticed is when providing memCacheWidth/memCacheHeight in flutter_cached_network_image it stops crashing but flutter's ResizeImage cacheWidth/cacheHeight don't care about the image fit we mention for the CachedNetworkImage

Source
https://github.com/flutter/flutter/issues/52802
https://github.com/flutter/flutter/pull/64352

@pushuhengyang and where would you call _checkMemory()?
Can you provide use with a minimal working example?

鎴睆2020-08-27 涓嬪崍6 03 19

It's best to encapsulate a widget

This does not work for me. I execute this code on every build of a CachedNetworkImage

  print(_imageCache.liveImageCount);
  print(_imageCache.currentSize);
  print(_imageCache.currentSizeBytes);

This results in

flutter: 1
flutter: 1
flutter: 3616960

But my app still crashes. I wil now start with profiling to check why this is happening.

When profiling I see a very big spike in memory usage. I have the same problem with Image.network:

https://github.com/flutter/flutter/issues/47378#issuecomment-692060368

This is our workaround for the moment. We wrote our own widget that downscales and caches the downscaled image to the cache

for this we will need the devicePixelRatio but that is coming from MediaQuery.of so you will need to fetch that value before you are using this widget. (We save this value in our config when MaterialApp is build. so we can always access this because the devicePixelRatio is will not change. )

import 'dart:typed_data';
import 'dart:ui';

import 'package:flutter_cache_manager/flutter_cache_manager.dart';
import 'package:pedantic/pedantic.dart';
import 'package:flutter/material.dart';

class MyProjectBetterImageWidget extends StatefulWidget {
final String imageUrl;
final BoxFit fit;
final double width;
final double height;
final Widget placeholder;

const MyProjectBetterImageWidget({
@required this.imageUrl,
@required this.fit,
@required this.width,
@required this.height,
@required this.placeholder,
Key key,
}) : super(key: key);

@override
_MyProjectBetterImageWidgetState createState() => _MyProjectBetterImageWidgetState();
}

class _MyProjectBetterImageWidgetState extends State {
var _isLoading = false;
var _hasError = false;

Uint8List _image;

Uint8List get image => _image;

bool get showPlaceholder => _hasError || _isLoading || _image == null;

@override
void initState() {
super.initState();
_getImage();
}

@override
void didUpdateWidget(MyProjectBetterImageWidget oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.imageUrl != widget.imageUrl) {
_getImage();
}
}

Future _getImage() async {
final originalUrl = widget.imageUrl;
final widgetWidth = widget.width;
final widgetHeight = widget.height;
if (originalUrl == null || originalUrl.endsWith('unknown.jpg')) {
return;
}
if (widgetWidth == double.infinity || widgetHeight == double.infinity || widgetWidth == null || widgetHeight == null) {
MyProjectLogger.logDebug('IMAGE-ERROR: $originalUrl ($widgetWidth/$widgetHeight');
setState(() => _hasError = true);
return;
}
try {
setState(() {
_isLoading = true;
_hasError = false;
});

  final width = (widgetWidth * FlavorConfig.instance.devicePixelRatio).toInt();
  final height = (widgetHeight * FlavorConfig.instance.devicePixelRatio).toInt();
  final url = '$originalUrl?w=$width&h=$height';
  var fileInfo = await DefaultCacheManager().getFileFromCache(url);
  var fromCache = true;
  if (!mounted) return;
  if (fileInfo == null) {
    fromCache = false;
    fileInfo = await DefaultCacheManager().downloadFile(url);
  }
  // ignore: invariant_booleans
  if (!mounted) return;
  if (fromCache) {
    _image = await fileInfo.file.readAsBytes();
    setState(() {
      _isLoading = false;
      _hasError = false;
    });
    return;
  }

  final bytes = await fileInfo.file.readAsBytes();
  final codec = await instantiateImageCodec(
    bytes,
    targetWidth: width >= height ? width : null,
    targetHeight: height > width ? height : null,
  );
  final frame = await codec.getNextFrame();
  final data = await frame.image.toByteData(format: ImageByteFormat.png);
  _image = data.buffer.asUint8List();
  if (!fromCache) {
    unawaited(_cacheImage(url));
  }
} catch (e) {
  MyProjectLogger.logError(message: 'Failed to parse image: $originalUrl', error: e);
  _hasError = true;
} finally {
  _isLoading = false;
}
// ignore: invariant_booleans
if (mounted) {
  setState(() {});
}

}

Future _cacheImage(String url) async {
try {
await DefaultCacheManager().putFile(url, _image, fileExtension: 'png');
} catch (e) {
MyProjectLogger.logError(message: 'Failed to cache image: $url', error: e);
}
}

@override
Widget build(BuildContext context) {
if (showPlaceholder) return widget.placeholder;
return Image.memory(
image,
width: widget.width,
height: widget.height,
fit: widget.fit,
);
}
}

Thanks for the workaround, @vanlooverenkoen! Are you using that in production now? No memory errors? I was trying to adapt it to my project and I have a couple of questions.

Questions:

  • For this to be a swap alternative to CachedNetworkImage, we'd need at least errorWidget. I can add it, maybe you evolved that already.
  • Is this dealing only with pngs?
  • weight and height are mandatory - but how to get those from the remote url? or are you fixing them as part of the UI definition?

Also, do you think this widget is relevant enough to move this to its own repo, so we don't divert the conversation from the main issue about flutter_cached_network_image?

I've got the same issue, the app loading about 100s images then exit suddenly (randomly, sometimes it crashes on about load 20-30 images,sometimes 200+ images), I read the log, the message about not ensure folder exists /mnt/shell/emulated/0/Android/data/..../caches and /mnt/shell/emulated/0/Android/data/..../files

I've added read, write externalcard permission, and the app not crash and exit suddenly.

If I didnt add the permission, why the app still run and crash randomly, why the lib not require permission at the first run.

updated: my app still randomly crash

@renefloor adding the memCacheWidth/memCacheHeight do reduce the memory hike as I check in Memory dart tools. But setting this value causing the image to be blur. Or we can say it pixelate. Is there any solution for it?

@RaashVision at what value did you set it? If you set it really small I can imaging that it becomes pixelated.
It is better to take other measures, for example that you don't show more than 10 images at the same time.
One way is for example by using ListView.builder: https://api.flutter.dev/flutter/widgets/ListView/ListView.builder.html

I get the same problem too. I have a listview builder and hundreds of png images on it. But when app try to load listview with images and scroll on listview, memory goes up to 2GB. App starts 300MB of memory usage but when app try to load images, memory starts to increase, and on iphone 11 pro kills the app around 2gb of RAM usage, iphone 6 kills the app around 650 Mb of RAM usage. I tried various methods but somehow couldn't solve the memory problem.

CachedNetworkImage( imageUrl: image_url, placeholder: (context, url) => CircularProgressIndicator(), errorWidget: (context, url, error) => Image.asset(noImage), ),

@halkportal970 Did you find any solution my case is the same as yours

@AdnanKazi Yes I found a solution.
I added memCacheWidth to all CachedNetworkImage widgets.
This realy reduced memory usage. Because my images width and height properties are very big.
But this is not a very good solution.
In web service side we must decrease images width and height according to mobile.

CachedNetworkImage(
                                    imageUrl: image_url,
                                    memCacheWidth: (Get.width * 0.6).toInt(),
                                    placeholder: (context, url) =>
                                        CircularProgressIndicator(),
                                    errorWidget: (context, url, error) =>
                                        Image.asset(noImage),
                                  ),

@halkportal970
Thank you so much for your advice in the meantime I have also found the solution which doesn't crash the app

Image.network('url',
       cacheHeight: widget.cacheHeight,
       cacheWidth: widget.cacheWidth,
       fit: BoxFit.cover,
          errorBuilder: (context, url, error) => Icon(Icons.error),
)

I will also try your solution as well as your solution looks better one

Having the same issue using this plugin. It raises a EXC_RESOURCE RESOURCE_TYPE_MEMORY on the thread that has name = io.flutter.1.io.

@pranavkpr1 _imageCache.clearLiveImages(); Can reduce a lot of memory
鎴睆2020-08-08 涓嬪崍4 22 39

It works!

I just tried an example with setting memCacheHeight and for me it largely reduced the memory footprint of the app.

@pranavkpr1 Hi... Can you pls share an example how u solved this issue........
I am also facing the same issue

@gathodeharrkirat did you try setting memCacheHeight?
For example:

      CachedNetworkImage(
        height: 200,
        width: 200,
        memCacheHeight: 200,
        imageUrl: url,
      ),

I am currently debugging an app ready for production but getting reports of crashes on older iphones (1gb RAM).
They are mostly caused by images not being correctly optimized by our client (like 4k pics for previews...).
Limiting them with memCacheHeight and memCacheWidth should do the trick for most, but it's kind of a sledgehammer method. Source Images should be optimized, not cut like this, as this also stretches non fitting resolutions.

A good idea is also using your own global cache manager instance, listening to low memory warning notifications from the system and dumping the cache to prevent crashes.

This is overall a very classic "limitation" of GPU frameworks and game engines. Pictures and Text (which is rendered and uploaded as bitmaps) have to be highly optimized and manually controlled.
A 1kb picture can easily take 20mb+ RAM. Look up the tech and math behind it if you professionally build apps with GPU frameworks!

Is it possible to add same memCacheHeight option to CachedNetworkImageProvider?

My usecase is that I want to preload some images before showing them and to do so I need ImageProvider in precacheImage() method.

Or maybe an option to get ImageProvider from CachedNetworkImage object as it's done in Image class (image property).

Was this page helpful?
0 / 5 - 0 ratings

Related issues

nashfive picture nashfive  路  5Comments

gregko picture gregko  路  6Comments

yossefEl picture yossefEl  路  4Comments

love-bkpp picture love-bkpp  路  5Comments

BerndWessels picture BerndWessels  路  6Comments