Flutter Image加载图片流程分析
从最基础的Image加载网络图片方法开始:
Image(image: NetworkImage(url));
查看Image源码可以看到,Image是一个StatefulWidget。设置了一个必选参数image,类型为ImageProvider。
const Image({
Key key,
@required this.image,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
......
})
ImageProvider是一个抽象类,内部定义图片数据的获取和加载方法。主要有下面几个方法:
ImageStream resolve(ImageConfiguration configuration){
//返回的是一个图片stream数据流
...
}
Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }){
//缓存移除方法
...
}
//获取缓存资源key
Future<T> obtainKey(ImageConfiguration configuration);
//load数据方法
ImageStreamCompleter load(T key, DecoderCallback decode);
我们的例子中使用的是NetworkImage作为ImageProvider,找到NetworkImage的源码位置。看一看load方法时如何实现的:
@override
ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
//chunkEvents用于传递数据的加载进度
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url,
informationCollector: () {
return <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
];
},
);
}
可以看到,load方法最终返回的是一个ImageStreamCompleter的实现类。我们先看看他的构造方法,特别关注一下codec参数。
MultiFrameImageStreamCompleter({
required Future<ui.Codec> codec,
required double scale,
String? debugLabel,
Stream<ImageChunkEvent>? chunkEvents,
InformationCollector? informationCollector,
})
codec参数是一个Future<ui.Codec>的必选参数,Codec 是处理图片编解码的类的一个handler,查看其内部方法,可以发现均是native方法。官方注释提示说:此类为engine创建,请勿手动实例化。也就是说图片的编解码逻辑不是在Dart 代码部分实现,而是在flutter engine中实现的。
/// A handle to an image codec.
///
/// This class is created by the engine, and should not be instantiated
/// or extended directly.
///
/// To obtain an instance of the [Codec] interface, see
/// [instantiateImageCodec].
@pragma('vm:entry-point')
class Codec extends NativeFieldWrapperClass2 {
@pragma('vm:entry-point')
Codec._();
int get frameCount native 'Codec_frameCount';
int get repetitionCount native 'Codec_repetitionCount';
Future<FrameInfo> getNextFrame() {
return _futurize(_getNextFrame);
}
String _getNextFrame(_Callback<FrameInfo> callback) native 'Codec_getNextFrame';
void dispose() native 'Codec_dispose';
}
再回到NetworkImage.load方法,MultiFrameImageStreamCompleter创建时传递的codec参数由_loadAsync方法提供
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents,
image_provider.DecoderCallback decode,
) async {
try {
assert(key == this);
final Uri resolved = Uri.base.resolve(key.url);
//通过HttpClient请求网络数据
final HttpClientRequest request = await _httpClient.getUrl(resolved);
headers?.forEach((String name, String value) {
request.headers.add(name, value);
});
final HttpClientResponse response = await request.close();
if (response.statusCode != HttpStatus.ok) {
throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
}
//发送数据加载进度事件
final Uint8List bytes = await consolidateHttpClientResponseBytes(
response,
onBytesReceived: (int cumulative, int? total) {
chunkEvents.add(ImageChunkEvent(
cumulativeBytesLoaded: cumulative,
expectedTotalBytes: total,
));
},
);
if (bytes.lengthInBytes == 0)
throw Exception('NetworkImage is an empty file: $resolved');
//返回解码后的数据
return decode(bytes);
} catch (e) {
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
现在再来看MultiFrameImageStreamCompleter。内部已经有了数据源和解码器,就看如何为image提供数据了。
void _handleCodecReady(ui.Codec codec) {
_codec = codec;
assert(_codec != null);
if (hasListeners) {
//这里了进入解码方法
_decodeNextFrameAndSchedule();
}
}
Future<void> _decodeNextFrameAndSchedule() async {
try {
_nextFrame = await _codec!.getNextFrame();
} catch (exception, stack) {
reportError(
context: ErrorDescription('resolving an image frame'),
exception: exception,
stack: stack,
informationCollector: _informationCollector,
silent: true,
);
return;
}
if (_codec!.frameCount == 1) {
//找到了发送图片帧方法
_emitFrame(ImageInfo(image: _nextFrame!.image, scale: _scale, debugLabel: debugLabel));
return;
}
_scheduleAppFrame();
}
当图片资源只有一帧的时候,会直接调用_emitFrame发送封装好的ImageInfo数据。
void _emitFrame(ImageInfo imageInfo) {
setImage(imageInfo);
_framesEmitted += 1;
}
@protected
void setImage(ImageInfo image) {
_currentImage = image;
if (_listeners.isEmpty)
return;
// Make a copy to allow for concurrent modification.
final List<ImageStreamListener> localListeners =
List<ImageStreamListener>.from(_listeners);
for (final ImageStreamListener listener in localListeners) {
try {
listener.onImage(image, false);
} catch (exception, stack) {
reportError(
context: ErrorDescription('by an image listener'),
exception: exception,
stack: stack,
);
}
}
}
这里可以看到,setImage内部会通过listener监听将数据回传。那listener是什么时候设置的呢?
之前有说到,image是一个StatefulWidget。查看内部的_ImageState的didChangeDependencies方法
@override
void didChangeDependencies() {
_updateInvertColors();
_resolveImage();
if (TickerMode.of(context))
//设置数据流监听
_listenToStream();
else
_stopListeningToStream();
super.didChangeDependencies();
}
void _listenToStream() {
if (_isListeningToStream)
return;
//这里便又回到了image_stream的listener设置
_imageStream.addListener(_getListener());
_isListeningToStream = true;
}
最终会在监听中调用setDate方法更新图片信息,完成一次图片的加载。
//帧返回处理
void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_imageInfo = imageInfo;
_loadingProgress = null;
_frameNumber = _frameNumber == null ? 0 : _frameNumber + 1;
_wasSynchronouslyLoaded |= synchronousCall;
});
}
//进度返回处理
void _handleImageChunk(ImageChunkEvent event) {
assert(widget.loadingBuilder != null);
setState(() {
_loadingProgress = event;
});
}