框架涉及 image 组件的代码:
packages/flutter/lib/src/widgets/image.dart
packages/flutter/lib/src/painting/image_cache.dart
packages/flutter/lib/src/painting/image_decoder.dart
packages/flutter/lib/src/painting/image_stream.dart
packages/flutter/lib/src/painting/image_provider.dart
packages/flutter/lib/src/painting/image_resolution.dart
1. 全局图片缓存
PaintingBinding.instance.imageCache
- image组件中的使用缓存
// 使用全局图片缓存来避免重复加载
final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
key,
() => loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize),
onError: handleError,
);
2. imagecache 主要维护三个结构
// _PendingImage 待加载图片
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
// _CachedImage 已加载图片。LRU Cache
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
// _liveImages 活跃中的图片(正在被界面使用)
final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};
3. imagecahce 缓存数据结构的具体内容:
- 缓存的不是原始图片字节数据,而是 ImageStreamCompleter 对象
abstract class _CachedImageBase {
final ImageStreamCompleter completer; // 图片流完成器
int? sizeBytes; // 图片大小(字节)
ImageStreamCompleterHandle? handle; // 保活句柄
}
class _PendingImage {
final ImageStreamCompleter completer;
final ImageStreamListener listener;
}
class _CachedImage extends _CachedImageBase {}
class _LiveImage extends _CachedImageBase {
late VoidCallback _handleRemove;
}
4. 使用同个 imageStreamCompleter可以共享哪些东西:
- 避免重复请求:同个网络请求/文件读取 io
- 减少图片解码开销
- 图片数据只有一份内存占用
- 共享底层 gpu 纹理,提升渲性能
gpu 纹理共享的实现:imageStreamCompleter 产物是 ui.image,ui.image clone 时,只有图片信息进行了复制,
/// A [dart:ui.Image] object with its corresponding scale.
///
/// ImageInfo objects are used by [ImageStream] objects to represent the
/// actual data of the image once it has been obtained.
///
/// * To share the [ImageInfo] or [ui.Image] between objects, use the [clone] method,
/// which will not clone the entire underlying image, but only reference to it and information about it.
/// * After passing a [ui.Image] or [ImageInfo] reference to another object,
/// release the reference.
typedef ImageListener = void Function(ImageInfo image, bool synchronousCall);
class ImageInfo {
final ui.Image image;
ImageInfo clone() {
return ImageInfo(
image: image.clone(),
scale: scale,
debugLabel: debugLabel,
);
}
}
class ImageStreamListener {
final ImageListener onImage;
}
5. imagecache 的索引,根据图片类型的不同,有以下几种
// 1. AssetBundleImageKey,用于 AssetImage 和 ExactAssetImage
class AssetBundleImageKey {
final AssetBundle bundle;
final String name; // 资源名称
final double scale; // 缩放比例
}
// 2. ResizeImageKey
class ResizeImageKey {
final Object _providerCacheKey; // 原始 ImageProvider 的 key
final ResizeImagePolicy _policy; // 调整策略
final int? _width; // 目标宽度
final int? _height; // 目标高度
final bool _allowUpscaling; // 是否允许放大
}
// 3. NetworkImage 实例本身
class NetworkImage {
Future<NetworkImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<NetworkImage>(this);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is NetworkImage
&& other.url == url
&& other.scale == scale;
}
}
// 4. FileImage 实例本身
class FileImage {
Future<FileImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<FileImage>(this);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is FileImage
&& other.file.path == file.path
&& other.scale == scale;
}
}
// 5. MemoryImage 示例本身
class MemoryImage {
Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<MemoryImage>(this);
}
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is MemoryImage
&& other.bytes == bytes
&& other.scale == scale;
}
}
6. 用NetworkImage举例下命中同一份缓存的情况。key校验代码
@override
bool operator ==(Object other) {
if (other.runtimeType != runtimeType) {
return false;
}
return other is NetworkImage
&& other.url == url
&& other.scale == scale;
}
- runtimeType:
-
- runtimeType 是 Dart 中所有对象都有的一个属性,它返回对象的运行时类型,这里两个NetworkImage一定是相等的
- URL: 网络请求的 url
- scale:图片的缩放
条件:两个NetworkImage对象,url和 scale 相同,比如:
// 这两个实例会命中同一份缓存
final img1 = NetworkImage('https://example.com/image.jpg');
final img2 = NetworkImage('https://example.com/image.jpg', headers: {'Authorization': 'Bearer token'});
// 这两个实例也会命中同一份缓存
final img3 = NetworkImage('https://example.com/image.jpg', headers: {'Custom': 'value1'});
final img4 = NetworkImage('https://example.com/image.jpg', headers: {'Custom': 'value2'});
NetworkImage 使用缓存的优缺点, 其他资源图片可以类比:
- 性能优化:
-
- 避免重复网络请求:相同 URL 的图片只下载一次
- GPU纹理共享:缓存的是解码后的 ui.Image,可直接渲染
- 内存管理:通过 LRU 算法和字节大小限制管理内存
- 潜在问题:
-
- 判断时不考虑Headers:不同 headers 的请求会命中同一缓存,这可能导致权限问题
- 缓存污染:第一次请求的结果会被后续相同 URL 的请求复用
2. 使用缩放尺寸解码
- 直接基于缩放尺寸进行解码,会比解码后再缩放高效
- 依赖外部调用者传入缩放宽高,不传入使用默认尺寸
通过 resizeImage 实现
class ResizeImage extends ImageProvider<ResizeImageKey> {
// 在解码时就指定目标尺寸,而不是在渲染时缩放
final int? width;
final int? height;
final ResizeImagePolicy policy;
final bool allowUpscaling;
static ImageProvider<Object> resizeIfNeeded(int? cacheWidth, int? cacheHeight, ImageProvider<Object> provider) {
if (cacheWidth != null || cacheHeight != null) {
return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
}
return provider;
}
}
解码时可以指定尺寸
/// Performs the decode process for use in [ImageProvider.loadImage].
///
/// This callback allows decoupling of the `getTargetSize` parameter from
/// implementations of [ImageProvider] that do not expose it.
///
/// See also:
///
/// * [ResizeImage], which uses this to load images at specific sizes.
typedef ImageDecoderCallback = Future<ui.Codec> Function(
ui.ImmutableBuffer buffer, {
ui.TargetImageSizeCallback? getTargetSize,
});
typedef TargetImageSizeCallback = TargetImageSize Function(
int intrinsicWidth,
int intrinsicHeight,
);
3. 流式加载
异步加载图片数据,避免 UI 阻塞
@nonVirtual
ImageStream resolve(ImageConfiguration configuration) {
final ImageStream stream = createStream(configuration);
// 立即返回stream,然后异步填充数据
}