主要分析Image.network
Image.network(
String src, {
Key? key,
double scale = 1.0,
this.frameBuilder,
this.loadingBuilder,
this.errorBuilder,
this.semanticLabel,
this.excludeFromSemantics = false,
this.width,
this.height,
this.color,
this.colorBlendMode,
this.fit,
this.alignment = Alignment.center,
this.repeat = ImageRepeat.noRepeat,
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
Map<String, String>? headers,
int? cacheWidth,
int? cacheHeight,
}) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key);
主要查看NetworkImage
abstract class NetworkImage extends ImageProvider<NetworkImage> {
/// Creates an object that fetches the image at the given URL.
///
/// The arguments [url] and [scale] must not be null.
const factory NetworkImage(String url, { double scale, Map<String, String>? headers }) = network_image.NetworkImage;
/// The URL from which the image will be fetched.
String get url;
/// The scale to place in the [ImageInfo] object of the image.
double get scale;
/// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
///
/// When running flutter on the web, headers are not used.
Map<String, String>? get headers;
@override
ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);
}
NetworkImage是一个继承ImageProvider的抽象类
- 进入network_image.NetworkImage,到了_network_image_io.dart文件。
@override
ImageStreamCompleter load(image_provider.NetworkImage key, image_provider.DecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
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),
];
},
);
}
- 可以看到_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);
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) {
// The network may be only temporarily unavailable, or the file will be
// added on the server later. Avoid having future calls to resolve
// fail to check the network again.
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) {
// Depending on where the exception was thrown, the image cache may not
// have had a chance to track the key in the cache at all.
// Schedule a microtask to give the cache a chance to add the key.
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
rethrow;
} finally {
chunkEvents.close();
}
}
- 从这里可以看出image.network,使用网络请求获取图片数据,最后使用decode进行编码图片返回
final HttpClientRequest request = await _httpClient.getUrl(resolved);
return decode(bytes);
- decode方法如下
typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
*跟踪查看ui.Codec
class Codec extends NativeFieldWrapperClass2 {
//
// 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')
Codec._();
*通过注释可以知道,相关源码需要查看PaintingBinding.instance!.instantiateImageCodec
Future<ui.Codec> instantiateImageCodec(Uint8List bytes, {
int? cacheWidth,
int? cacheHeight,
bool allowUpscaling = false,
}) {
assert(cacheWidth == null || cacheWidth > 0);
assert(cacheHeight == null || cacheHeight > 0);
assert(allowUpscaling != null);
return ui.instantiateImageCodec(
bytes,
targetWidth: cacheWidth,
targetHeight: cacheHeight,
allowUpscaling: allowUpscaling,
);
}
*经过一系列的调用后,最终会直接调用原生方法,进行图片解析
void _instantiateCodec(Codec outCodec, int targetWidth, int targetHeight) native 'ImageDescriptor_instantiateCodec';
- 这里着重看一下,PaintingBinding.instance!.imageCache,这个方法主要是全局图片缓存的控制。可以根据这个进行图片缓存的自定义操作
scheduleMicrotask(() {
PaintingBinding.instance!.imageCache!.evict(key);
});
- 判断缓存的key标准是通过url跟scale来进行处理, 不同的key代表不同的图片数据缓存。
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType)
return false;
return other is NetworkImage
&& other.url == url
&& other.scale == scale;
}
- 通过查看ImageProvide.resolve方法
ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = createStream(configuration);
// Load the key (potentially asynchronously), set up an error handling zone,
// and call resolveStreamForKey.
_createErrorHandlerAndKey(
configuration,
(T key, ImageErrorListener errorHandler) {
resolveStreamForKey(configuration, stream, key, errorHandler);
},
(T? key, Object exception, StackTrace? stack) async {
await null; // wait an event turn in case a listener has been added to the image stream.
final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
stream.setCompleter(imageCompleter);
InformationCollector? collector;
assert(() {
collector = () sync* {
yield DiagnosticsProperty<ImageProvider>('Image provider', this);
yield DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration);
yield DiagnosticsProperty<T>('Image key', key, defaultValue: null);
};
return true;
}());
imageCompleter.setError(
exception: exception,
stack: stack,
context: ErrorDescription('while resolving an image'),
silent: true, // could be a network error or whatnot
informationCollector: collector,
);
},
);
return stream;
}
*创建key的流程
///创建key的流程
void _createErrorHandlerAndKey(
ImageConfiguration configuration,
_KeyAndErrorHandlerCallback<T> successCallback,///缓存会在这里执行
_AsyncKeyErrorHandler<T> errorCallback,
) {
/// 创建一个新Zone,主要是为了当发生错误时不会干扰MainZone
final Zone dangerZone = Zone.current.fork(
specification: ZoneSpecification(
handleUncaughtError: (Zone zone, ZoneDelegate delegate, Zone parent, Object error, StackTrace stackTrace) {
handleError(error, stackTrace);
}
)
);
dangerZone.runGuarded(() {
// 先验证是否已经有缓存
Future<T> key;
try {
// 生成缓存key,后面会根据此key来检测是否有缓存
key = obtainKey(configuration);
} catch (error, stackTrace) {
handleError(error, stackTrace);
return;
}
key.then<void>((T key) {
obtainedKey = key;
try {
//成功回调
successCallback(key, handleError);
} catch (error, stackTrace) {
handleError(error, stackTrace);
}
}).catchError(handleError);
});
}
- 最终会调用到PaintingBinding.instance.imageCache.putIfAbsent方法,由于PaintingBinding.instance是一个单例,所以imageCache也是一个单例,综上可以知道,flutter图片缓存是全局单例唯一的
总结
由于缓存是放在PaintingBinding.instance.imageCache中的map集合里面的,所以只是缓存在内存中,并没有缓存在本地文件中。因此性能效率上来说并不高效,所以也需要我们做相应优化。下一篇会记录
网友评论