ImageStreamCompleter
ImageStreamCompleter是Flutter中一个用于管理图像数据解析和渲染的类。
当一个图像资源被加载到Flutter中时,它的二进制数据需要被解码并转换为可以渲染的图像格式。在这个过程中,可能会发生一些错误,比如网络连接失败、图片格式不支持等,ImageStreamCompleter类就是用来辅助管理这些过程的。
从Flutter加载图片流程之ImageProvider源码解析(一)可以知道:
当一个ImageStreamCompleter对象被创建时,它会生成一个ImageStream对象,用于接收来自图像数据源的图像数据。ImageStreamCompleter类提供了多种方法来管理这个ImageStream对象,包括添加/删除监听器、通知监听器图像数据的更新等。
当图像数据解析完成后,ImageStreamCompleter会创建一个ImageInfo对象,该对象包含了用于渲染图像的ImageProvider对象、图像的宽度和高度以及渲染图像所需要的其他元数据。然后,ImageStreamCompleter会将这个ImageInfo对象传递给ImageStream对象,并通知所有监听器数据的更新。
在使用Image组件加载和显示图像时,Image组件会先创建一个ImageStreamCompleter对象,并将其作为ImageProvider对象的load方法的返回值。当Image组件需要渲染图像时,它会从ImageStreamCompleter对象中获取ImageInfo对象,并使用它来渲染图像。
总的来说,ImageStreamCompleter类是Flutter中一个非常重要的类,它用于管理图像数据的解析和渲染过程,帮助开发者更方便地使用Image组件加载和显示图像。
addListener
/// Adds a listener callback that is called whenever a new concrete [ImageInfo]
/// object is available or an error is reported. If a concrete image is
/// already available, or if an error has been already reported, this object
/// will notify the listener synchronously.
///
/// If the [ImageStreamCompleter] completes multiple images over its lifetime,
/// this listener's [ImageStreamListener.onImage] will fire multiple times.
///
/// {@macro flutter.painting.imageStream.addListener}
void addListener(ImageStreamListener listener) {
_checkDisposed();
_hadAtLeastOneListener = true;
_listeners.add(listener);
if (_currentImage != null) {
try {
listener.onImage(_currentImage!.clone(), !_addingInitialListeners);
} catch (exception, stack) {
reportError(
context: ErrorDescription('by a synchronously-called image listener'),
exception: exception,
stack: stack,
);
}
}
if (_currentError != null && listener.onError != null) {
try {
listener.onError!(_currentError!.exception, _currentError!.stack);
} catch (newException, newStack) {
if (newException != _currentError!.exception) {
FlutterError.reportError(
FlutterErrorDetails(
exception: newException,
library: 'image resource service',
context: ErrorDescription('by a synchronously-called image error listener'),
stack: newStack,
),
);
}
}
}
}
ImageStreamCompleter类的addListener方法用于添加一个回调函数,该回调函数在每当一个新的具体的ImageInfo对象可用或报告错误时被调用。如果一个具体的图像已经可用,或者已经报告了错误,那么该对象将同步通知监听器。
如果ImageStreamCompleter在其生命周期内完成了多个图像,则此侦听器的ImageStreamListener.onImage将会多次触发。
_checkDisposed()
void _checkDisposed() {
if (_disposed) {
throw StateError(
'Stream has been disposed.\n'
'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.\n'
'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.',
);
}
}
_checkDisposed() 是一个内部方法,用于检查 ImageStreamCompleter 对象是否已经被销毁。如果已经被销毁,则会抛出异常。
addListener
/// Adds a listener callback that is called whenever a new concrete [ImageInfo]
/// object is available or an error is reported. If a concrete image is
/// already available, or if an error has been already reported, this object
/// will notify the listener synchronously.
///
/// If the [ImageStreamCompleter] completes multiple images over its lifetime,
/// this listener's [ImageStreamListener.onImage] will fire multiple times.
///
/// {@macro flutter.painting.imageStream.addListener}
void addListener(ImageStreamListener listener) {
_checkDisposed();
_hadAtLeastOneListener = true;
_listeners.add(listener);
if (_currentImage != null) {
try {
listener.onImage(_currentImage!.clone(), !_addingInitialListeners);
} catch (exception, stack) {
reportError(
context: ErrorDescription('by a synchronously-called image listener'),
exception: exception,
stack: stack,
);
}
}
if (_currentError != null && listener.onError != null) {
try {
listener.onError!(_currentError!.exception, _currentError!.stack);
} catch (newException, newStack) {
if (newException != _currentError!.exception) {
FlutterError.reportError(
FlutterErrorDetails(
exception: newException,
library: 'image resource service',
context: ErrorDescription('by a synchronously-called image error listener'),
stack: newStack,
),
);
}
}
}
}
从源码可以知道:
ImageStreamCompleter类的addListener方法用于添加一个回调函数,该回调函数在每当一个新的具体的ImageInfo对象可用或报告错误时被调用。如果一个具体的图像已经可用,或者已经报告了错误,那么该对象将同步通知监听器。
如果ImageStreamCompleter在其生命周期内完成了多个图像,则此侦听器的ImageStreamListener.onImage将会多次触发。
在Flutter中,当调用ImageProvider的resolve方法时,将返回一个ImageStream对象。ImageStream对象管理着图像在解析过程中的状态和生命周期,可以通过调用addListener方法添加回调函数来监听图像状态的变化。一旦图像被解析并且ImageInfo对象可用,ImageStream会将ImageInfo对象封装在一个ImageStreamCompleter对象中,并将其作为参数传递给回调函数。回调函数可以使用ImageInfo对象来显示图像,也可以使用dispose方法释放资源。
_hadAtLeastOneListener
_hadAtLeastOneListener = true;
_hadAtLeastOneListener = true; 是将私有成员 _hadAtLeastOneListener 的值设置为 true,表示至少有一个监听器被添加到该 ImageStreamCompleter 中。
_listeners.add(listener)
这行代码将传入的 listener 对象添加到 _listeners 列表中,以便在图像加载完成或加载出错时通知它。
removeListener
/// Stops the specified [listener] from receiving image stream events.
///
/// If [listener] has been added multiple times, this removes the _first_
/// instance of the listener.
///
/// Once all listeners have been removed and all [keepAlive] handles have been
/// disposed, this image stream is no longer usable.
void removeListener(ImageStreamListener listener) {
_checkDisposed();
for (int i = 0; i < _listeners.length; i += 1) {
if (_listeners[i] == listener) {
_listeners.removeAt(i);
break;
}
}
if (_listeners.isEmpty) {
final List<VoidCallback> callbacks = _onLastListenerRemovedCallbacks.toList();
for (final VoidCallback callback in callbacks) {
callback();
}
_onLastListenerRemovedCallbacks.clear();
_maybeDispose();
}
}
- 停止指定的 [listener] 接收图像流事件。
- 如果 [listener] 被添加了多次,这将删除 listener 的第一个实例。
- 一旦所有侦听器都被删除且所有 [keepAlive] 句柄都被处理,此图像流将不再可用。
_maybeDispose
void _maybeDispose() {
if (!_hadAtLeastOneListener || _disposed || _listeners.isNotEmpty || _keepAliveHandles != 0) {
return;
}
_currentImage?.dispose();
_currentImage = null;
_disposed = true;
}
_maybeDispose() 是 ImageStreamCompleter 内部的一个方法,用于检查该 ImageStreamCompleter 对象是否需要释放资源。如果当前已经没有监听器,并且所有的 keepAlive 引用已经被释放,就会调用 dispose() 方法释放资源。
具体而言, _maybeDispose() 方法会检查当前是否存在监听器,如果不存在,则获取当前所有的 keepAlive 引用的个数。如果引用个数为 0,则调用 dispose() 方法进行资源释放。
setImage(特别重要)
/// Calls all the registered listeners to notify them of a new image.
@protected
@pragma('vm:notify-debugger-on-exception')
void setImage(ImageInfo image) {
_checkDisposed();
_currentImage?.dispose();
_currentImage = image;
if (_listeners.isEmpty) {
return;
}
// Make a copy to allow for concurrent modification.
final List<ImageStreamListener> localListeners =
List<ImageStreamListener>.of(_listeners);
for (final ImageStreamListener listener in localListeners) {
try {
listener.onImage(image.clone(), false);
} catch (exception, stack) {
reportError(
context: ErrorDescription('by an image listener'),
exception: exception,
stack: stack,
);
}
}
}
- 该方法用于通知所有已注册的监听器,有一个新的图片可用了。
- 它会先检查是否已经被销毁,然后会释放旧的图片(如果存在)并设置当前图片为新的图片。
- 如果当前没有监听器,则直接返回。
- 如果存在监听器,它会将所有监听器的副本保存到本地变量中,并for循环依次调用它们的onImage()方法来通知它们有一个新的图片可用。如果调用期间出现异常,则会使用reportError()方法来报告异常。最后,该方法返回。
listener.onImage(image.clone(), false);重点是通知监听器有个图片可以使用了。_ImageState中的_handleImageFrame方法就会接收到新的图片了。
ImageInfo? _currentImage
ImageInfo 是一个用于表示图像数据及其缩放比例的类,它包含一个 dart:ui.Image 对象和对应的缩放比例。ImageStream 对象使用 ImageInfo 对象来表示图像数据一旦被获取后的实际数据。
使用 ImageInfo 对象的接收方必须调用 dispose 方法来释放资源。如果要安全地与其他客户端共享对象,则应在调用 dispose 之前使用 clone 方法来创建一个副本。
ImageInfo 类中的 image 属性是一个 dart:ui.Image 对象,它表示一个已解码的图像。scale 属性表示该图像的缩放比例,通常在高分辨率设备上会使用更高的缩放比例来显示更清晰的图像。debugLabel 属性可以用来标识该 ImageInfo 对象,以方便调试。
总的来说,ImageInfo 类是 Flutter 中一个用于表示图像数据及其缩放比例的类,它常常被用于 ImageStream 对象中来表示解码后的图像数据。开发者在使用 ImageInfo 对象时,应注意使用完毕后及时释放资源。
clone
clone 方法是 ImageInfo 类中的一个方法,用于创建一个 ImageInfo 对象的副本,其中包含一个克隆的 dart:ui.Image 对象和相同的缩放比例。
使用 clone 方法创建的 ImageInfo 对象可以安全地共享给其他客户端,并在需要访问其基础图像数据时使用。因为 clone 方法创建的对象使用的是副本而非原始的 dart:ui.Image 对象,所以即使其他客户端释放了它们自己的引用,该 ImageInfo 对象中的图像数据仍然可以被访问。
当一个客户端需要与另一个客户端共享 ImageInfo 对象并且仍然需要在某个后续时刻访问其基础图像数据时,应使用 clone 方法来创建一个副本。该方法通常用于实现在应用程序中复制或转移图像数据的功能,以便多个客户端可以访问相同的图像数据,而不必互相干扰或争夺图像数据的访问权限。
isCloneOf
/// Whether this [ImageInfo] is a [clone] of the `other`.
///
/// This method is a convenience wrapper for [Image.isCloneOf], and is useful
/// for clients that are trying to determine whether new layout or painting
/// logic is required when receiving a new image reference.
///
/// {@tool snippet}
///
/// The following sample shows how to appropriately check whether the
/// [ImageInfo] reference refers to new image data or not.
///
/// ```dart
/// ImageInfo? _imageInfo;
/// set imageInfo (ImageInfo? value) {
/// // If the image reference is exactly the same, do nothing.
/// if (value == _imageInfo) {
/// return;
/// }
/// // If it is a clone of the current reference, we must dispose of it and
/// // can do so immediately. Since the underlying image has not changed,
/// // We don't have any additional work to do here.
/// if (value != null && _imageInfo != null && value.isCloneOf(_imageInfo!)) {
/// value.dispose();
/// return;
/// }
/// _imageInfo?.dispose();
/// _imageInfo = value;
/// // Perform work to determine size, or paint the image.
/// }
/// ```
/// {@end-tool}
bool isCloneOf(ImageInfo other) {
return other.image.isCloneOf(image)
&& scale == scale
&& other.debugLabel == debugLabel;
}
isCloneOf 方法是 ImageInfo 类中的一个方法,用于确定该 ImageInfo 对象是否是另一个 ImageInfo 对象的副本。该方法通过比较两个对象的属性,包括它们的 dart:ui.Image 对象是否是同一个对象、缩放比例是否相同、调试标签是否相同等,来判断它们是否是同一个图像的多个引用。
在实际应用中,isCloneOf 方法通常用于确定是否需要重新布局或绘制图像数据。例如,在接收到一个新的 ImageInfo 对象时,客户端可能需要比较它与之前的引用是否相同,如果它是之前引用的克隆副本,那么客户端可以直接释放它而无需进行额外的布局或绘制工作。通过调用 isCloneOf 方法,客户端可以方便地检查两个 ImageInfo 对象是否表示相同的图像数据,并根据需要采取相应的行动。
ui.Image image
ui.Image image 是 ImageInfo 类的一个属性,表示一个 dart:ui.Image 对象。dart:ui.Image 是 Flutter 的核心图像类,它封装了一个平台相关的图像对象,可以用于显示到屏幕上或保存到文件中。在 Flutter 中,通常通过 ImageProvider 加载图像数据,然后将其转换为 Image 对象并传递给 Image 或 DecorationImage 等小部件来显示图像。而在 ImageInfo 中,ui.Image image 则表示了已经成功加载并解码的图像数据,它是一个与缩放因子相关联的图像对象,可以直接传递给 Canvas 等渲染对象进行绘制。
sizeBytes(图片在APP占用的内存,就是这个。后面需要优化的也是这个)
int get sizeBytes => image.height * image.width * 4; 是 ImageInfo 类的一个 getter 方法,它返回一个整型值,表示当前 ImageInfo 对象所包含的图像数据所占用的内存空间大小,单位是字节。
其中,image.width 表示图像的宽度,image.height 表示图像的高度,这两个值相乘就是图像的像素总数。由于在 Flutter 中,每个像素都占用 4 个字节的内存空间,因此将像素总数乘以 4 就可以得到图像所占用的内存空间大小。
在 ImageInfo 类中,sizeBytes 计算原始图像像素的大小时,为什么要乘以 4?这是因为图像的每个像素通常由 RGBA 四个通道组成,每个通道占用 1 个字节 (8 位) 。
在大多数情况下,Flutter 处理的图像数据格式是 32 位 (4 字节) RGBA:
- R(红色通道) :1 字节(8 位)
- G(绿色通道) :1 字节(8 位)
- B(蓝色通道) :1 字节(8 位)
- A(透明度通道) :1 字节(8 位)
- 合计:4 字节(32 位)
由于每个像素占 4 个字节 (RGBA) ,所以乘以 4
需要注意的是,由于图像数据在内存中的存储方式可能会受到平台、像素格式、压缩等因素的影响,因此实际的图像内存大小可能会有所不同。此处的计算方式仅是一种简单的估算方式。
dispose
/// Disposes of this object.
///
/// Once this method has been called, the object should not be used anymore,
/// and no clones of it or the image it contains can be made.
void dispose() {
assert((image.debugGetOpenHandleStackTraces()?.length ?? 1) > 0);
image.dispose();
}
dispose() 是 ImageInfo 类的一个方法,用于释放当前对象所持有的图像数据。
在该方法内部,它首先调用 debugGetOpenHandleStackTraces() 方法获取当前图像数据的使用情况,并通过 assert() 断言来确保该数据至少有一个打开的句柄,也就是说至少有一个对象在使用该数据。这是为了避免在某些情况下,该方法被错误地调用而导致内存泄漏或程序崩溃。
接着,该方法调用 image.dispose() 来释放当前对象所持有的图像数据。注意,该方法并不负责释放当前ImageInfo 对象本身的内存空间,因为在 Dart 语言中,由垃圾回收器来负责自动回收不再使用的对象。因此,在释放当前对象之前,需要确保没有任何对象引用了它。
调用 dispose() 方法后, ImageInfo 对象的 image 属性中包含的图像数据dart:ui.Image 对象会被释放掉,因此不能再访问它了。但是 ImageInfo 对象本身并没有被置为 null,因此可以继续使用它,但是对 image 属性的访问会导致运行时错误。为了避免这种情况,建议在调用 dispose() 方法后将 ImageInfo 对象置为 null,以确保不会意外地访问已经被释放掉的图像数据。
总结
ImageStreamCompleter 是一个负责管理异步加载图片并将加载结果通知到多个监听器的类。它包含一个 _currentImage 变量,用于保存最新的 ImageInfo 对象,以及一个 _listeners 列表,用于保存所有已注册的监听器。
在加载图片期间,ImageStreamCompleter 会逐步生成 ImageChunkEvent 事件,这些事件包含了加载进度信息。当加载完成后,它会生成一个 ImageInfo 对象,然后通知所有已注册的监听器。
ImageStreamCompleter 还可以管理 ImageInfo 对象的生命周期,通过 addListener() 方法注册的监听器可以获取一个 ImageInfo 的克隆对象,并在不需要时调用 dispose() 方法释放资源。ImageStreamCompleter 还提供了 removeListener() 方法,用于删除指定的监听器。
总的来说,ImageStreamCompleter 提供了一种便捷的方式来管理异步加载图片,并且可以将加载结果通知到多个监听器,以便更好地管理图片的使用和资源释放。