-
Notifications
You must be signed in to change notification settings - Fork 26.7k
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
Exception has occurred when call ImageProvider.evict at state.dispose #77576
Comments
@zmtzawqlp 77576.mp4flutter doctor -v
|
image is ready loaded, please try to change a big image. and i will check it on 2.0.1, maybe it's already fixed |
It is found in 2.0.1 at my side too. please make sure the image is not loaded completed. |
@zmtzawqlp |
I have updated the demo, maybe it's more easy to reproduce this issue |
Reproduce on 2.0.1. Scroll the image list then error will appear. [ERROR:flutter/lib/ui/ui_dart_state.cc(186)] Unhandled Exception: Bad state: Stream has been disposed.
An ImageStream is considered disposed once at least one listener has been added and subsequently all listeners have been removed and no handles are outstanding from the keepAlive method.
To resolve this error, maintain at least one listener on the stream, or create an ImageStreamCompleterHandle from the keepAlive method, or create a new stream for the image.
#0 ImageStreamCompleter._checkDisposed (package:flutter/src/painting/image_stream.dart:572:7)
#1 ImageStreamCompleter.reportImageChunkEvent (package:flutter/src/painting/image_stream.dart:705:5)
#2 _rootRunUnary (dart:async/zone.dart:1362:47)
#3 _CustomZone.runUnary (dart:async/zone.dart:1265:19)
#4 _CustomZone.runUnaryGuarded (dart:async/zone.dart:1170:7)
#5 _BufferingStreamSubscription._sendData (dart:async/stream_impl.dart:341:11)
#6 _DelayedData.perform (dart:async/stream_impl.dart:591:14)
#7 _StreamImplEvents.handleNext (dart:async/stream_impl.dart:706:11)
#8 _PendingEvents.schedule.<anonymous closure> (dart:async/stream_impl.dart:663:7)
#9 _rootRun (dart:async/zone.dart:1346:47)
#10 _CustomZone.run (dart:async/zone.dart:1258:19)
#11 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
#12 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1202:23)
#13 _rootRun (dart:async/zone.dart:1354:13)
#14 _CustomZone.run (dart:async/zone.dart:1258:19)
#15 _CustomZone.runGuarded (dart:async/zone.dart:1162:7)
#16 _CustomZone.bindCallbackGuarded.<anonymous closure> (dart:async/zone.dart:1202:23)
#17 _microtaskLoop (dart:async/schedule_microtask.dart:40:21)
#18 _startMicrotaskLoop (dart:async/schedule_microtask.dart:49:5) Example codeimport 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key? key, this.title}) : super(key: key);
final String? title;
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<String> images = <String>[
'https://photo.tuchong.com/4870004/f/298584322.jpg',
'https://photo.tuchong.com/16389644/f/1303840119.jpg',
'https://photo.tuchong.com/1924454/f/153280211.jpg',
'https://photo.tuchong.com/1824846/f/601739531.jpg',
'https://photo.tuchong.com/2353448/f/147183522.jpg',
'https://photo.tuchong.com/2732194/f/514055506.jpg',
'https://photo.tuchong.com/19454111/f/940639122.jpg',
'https://photo.tuchong.com/12772247/f/950208723.jpg',
'https://photo.tuchong.com/16389644/f/1303840119.jpg',
'https://photo.tuchong.com/1924454/f/153280211.jpg',
'https://photo.tuchong.com/1824846/f/601739531.jpg',
'https://photo.tuchong.com/2353448/f/147183522.jpg',
'https://photo.tuchong.com/2732194/f/514055506.jpg',
'https://photo.tuchong.com/19454111/f/940639122.jpg',
'https://photo.tuchong.com/12772247/f/950208723.jpg',
'https://photo.tuchong.com/16389644/f/1303840119.jpg',
'https://photo.tuchong.com/1924454/f/153280211.jpg',
'https://photo.tuchong.com/1824846/f/601739531.jpg',
'https://photo.tuchong.com/2353448/f/147183522.jpg',
'https://photo.tuchong.com/2732194/f/514055506.jpg',
'https://photo.tuchong.com/19454111/f/940639122.jpg',
'https://photo.tuchong.com/12772247/f/950208723.jpg',
'https://photo.tuchong.com/1824846/f/601739531.jpg',
'https://photo.tuchong.com/20789574/f/1191315286.jpg',
'https://photo.tuchong.com/2353448/f/147183522.jpg',
'https://photo.tuchong.com/15809857/f/158194644.jpg',
'https://photo.tuchong.com/16389644/f/1303840119.jpg',
'https://photo.tuchong.com/1924454/f/153280211.jpg',
];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title!),
),
body: ListView.builder(
itemBuilder: (context, index) {
return ImageItem(images[index]);
},
itemCount: images.length,
),
);
}
}
class ImageItem extends StatefulWidget {
ImageItem(this.uri);
final String uri;
@override
_ImageItemState createState() => _ImageItemState();
}
class _ImageItemState extends State<ImageItem> {
late final NetworkImage imageProvider = NetworkImage(widget.uri);
@override
void dispose() {
imageProvider.evict();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Image(image: imageProvider);
}
}
|
@zmtzawqlp
Your original exception seems to be different and related to failure in assertion, |
It's the same error as what Alex said. I want to know how to clear the image memory safety,thanks |
@zmtzawqlp
|
This is no refer to the demo case, i just want to call ImageProvider. evict when List item is disposed and removed from Widget tree. Is there any solution that we can deallocate image memory when this image is removed from Widget tree. |
@zmtzawqlp |
This api is worked in 1.22.6, but it's breaking in 2.0.0 . so i need to know why and how to use it. |
flutter doctor -v
|
/cc @dnfield |
it seems that we can evict image only if it has completed. /// An image that has been submitted to [ImageCache.putIfAbsent], but
/// not yet completed.
final bool pending;
/// An image that has been submitted to [ImageCache.putIfAbsent], has
/// completed, fits based on the sizing rules of the cache, and has not been
/// evicted.
///
/// Such images will be kept alive even if [live] is false, as long
/// as they have not been evicted from the cache based on its sizing rules.
final bool keepAlive;
/// An image that has been submitted to [ImageCache.putIfAbsent] and has at
/// least one listener on its [ImageStreamCompleter].
///
/// Such images may also be [keepAlive] if they fit in the cache based on its
/// sizing rules. They may also be [pending] if they have not yet resolved.
final bool live; we have no api to do such thing now, what i can see is do as following: imageProvider
.obtainCacheStatus(configuration: ImageConfiguration.empty)
.then((ImageCacheStatus? value) {
if (value?.keepAlive ?? false) {
widget.image.evict();
}
}); But how and when can i release these image which are not completed at that moment? |
I'm a little confused about [ImageCache.evict], as what annotation said, it seems that if i evict with includeLive=false, it will not throw exception, but it failed. Maybe i have misconstrued for it. It's very kind if someone could clarify it. bool evict(Object key, { bool includeLive = true }) {
assert(includeLive != null);
if (includeLive) {
// Remove from live images - the cache will not be able to mark
// it as complete, and it might be getting evicted because it
// will never complete, e.g. it was loaded in a FakeAsync zone.
// In such a case, we need to make sure subsequent calls to
// putIfAbsent don't return this image that may never complete.
final _LiveImage? image = _liveImages.remove(key);
image?.dispose();
}
final _PendingImage? pendingImage = _pendingImages.remove(key);
if (pendingImage != null) {
if (!kReleaseMode) {
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
'type': 'pending'
});
}
pendingImage.removeListener();
return true;
}
final _CachedImage? image = _cache.remove(key);
if (image != null) {
if (!kReleaseMode) {
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
'type': 'keepAlive',
'sizeInBytes': image.sizeBytes,
});
}
_currentSizeBytes -= image.sizeBytes!;
image.dispose();
return true;
}
if (!kReleaseMode) {
Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
'type': 'miss',
});
}
return false;
} |
What's up with this ? Tagged |
Hm. I was tagged in this while I was away on some paternity leave and missed it when I got back. I can take a look now, I suspect there's a pretty simple fix. |
Fwiw, here's what I have, and it sometimes throws the @override
Future<void> dispose() async {
Future<void>.delayed(Duration.zero, () async {
await NetworkImage(widget.src).evict();
});
super.dispose();
} |
I also think this isn't a "hard" crasher - we shouldn't let this happen, but it's not going to actually crash your application or prevent you from continuing to run. |
I agree. What will be the preferred way to evict the images at dispose time once your fix lands though? Just making sure everything is clear here. |
The code in this issue is fine. There's just a bug where MultiFrame isn't unhooking itself from chunk events, which network images report during download. |
This thread has been automatically locked since there has not been any recent activity after it was closed. If you are still experiencing a similar issue, please open a new bug, including the output of |
I want to remove image data from memory when image item is dispose.
Steps to Reproduce
1.run demo, scroll list
Exception has occurred.
This is not found in v1.22.6
The text was updated successfully, but these errors were encountered: