Flutter中网络图片加载和缓存源码分析(内存缓存+文件缓存(1)

下面通过源码我们来看下Image.network加载网络图片的具体实现。

Image.network(String src, {
Key key,
double scale = 1.0,
.
.
}) : image = NetworkImage(src, scale: scale, headers: headers),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null),
super(key: key);

/// The image to display.
final ImageProvider image;

首先,使用Image.network命名构造函数创建Image对象时,会同时初始化实例变量image,image是一个ImageProvider对象,该ImageProvider就是我们所需要的图片的提供者,它本身是一个抽象类,子类包括NetworkImageFileImageExactAssetImageAssetImageMemoryImage等,网络加载图片使用的就是NetworkImage

Image作为一个StatefulWidget其状态由**_ImageState控制,_ImageState继承自State类,其生命周期方法包括initState()**、didChangeDependencies()build()deactivate()dispose()didUpdateWidget()等。我们重点来_ImageState中函数的执行。

由于插入渲染树时会先调用initState()函数,然后调用didChangeDependencies()函数,_ImageState中并没有重写**initState()函数,所以didChangeDependencies()函数会执行,看下didChangeDependencies()**里的内容

@override
void didChangeDependencies() {
_invertColors = MediaQuery.of(context, nullOk: true)?.invertColors
?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
_resolveImage();

if (TickerMode.of(context))
_listenToStream();
else
_stopListeningToStream();

super.didChangeDependencies();
}

**_resolveImage()**会被调用,函数内容如下

void _resolveImage() {
final ImageStream newStream =
widget.image.resolve(createLocalImageConfiguration(
context,
size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null
));
assert(newStream != null);
_updateSourceStream(newStream);
}

函数中先创建了一个ImageStream对象,该对象是一个图片资源的句柄,其持有着图片资源加载完毕后的监听回调和图片资源的管理者。而其中的ImageStreamCompleter对象就是图片资源的一个管理类,也就是说,_ImageState通过ImageStreamImageStreamCompleter管理类建立了联系。

再回头看一下ImageStream对象是通过widget.image.resolve方法创建的,也就是对应NetworkImageresolve方法,我们查看NetworkImage类的源码发现并没有resolve方法,于是查找其父类,在mageProvider类中找到了。

ImageStream resolve(ImageConfiguration configuration) {
assert(configuration != null);
final ImageStream stream = ImageStream();
T obtainedKey;
Future handleError(dynamic exception, StackTrace stack) async {
.
.
}
obtainKey(configuration).then((T key) {
obtainedKey = key;
final ImageStreamCompleter completer = PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);
if (completer != null) {
stream.setCompleter(completer);
}
}).catchError(handleError);
return stream;
}

ImageStream中的图片管理者ImageStreamCompleter通过PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);方法创建,imageCache是Flutter框架中实现的用于图片缓存的单例,查看其中的putIfAbsent方法

ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
assert(key != null);
assert(loader != null);
ImageStreamCompleter result = _pendingImages[key]?.completer;
// Nothing needs to be done because the image hasn’t loaded yet.
if (result != null)
return result;
// Remove the provider from the list so that we can move it to the
// recently used position below.
final _CachedImage image = _cache.remove(key);
if (image != null) {
_cache[key] = image;
return image.completer;
}
try {
result = loader();
} catch (error, stackTrace) {
if (onError != null) {
onError(error, stackTrace);
return null;
} else {
rethrow;
}
}
void listener(ImageInfo info, bool syncCall) {
// Images that fail to load don’t contribute to cache size.
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
final _CachedImage image = _CachedImage(result, imageSize);
// If the image is bigger than the maximum cache size, and the cache size
// is not zero, then increase the cache size to the size of the image plus
// some change.
if (maximumSizeBytes > 0 && imageSize > maximumSizeBytes) {
_maximumSizeBytes = imageSize + 1000;
}
_currentSizeBytes += imageSize;
final _PendingImage pendingImage = _pendingImages.remove(key);
if (pendingImage != null) {
pendingImage.removeListener();
}

_cache[key] = image;
_checkCacheSize();
}
if (maximumSize > 0 && maximumSizeBytes > 0) {
_pendingImages[key] = _PendingImage(result, listener);
result.addListener(listener);
}
return result;
}

通过以上代码可以看到会通过key来查找缓存中是否存在,如果存在则返回,如果不存在则会通过执行**loader()**方法创建图片资源管理者,而后再将缓存图片资源的监听方法注册到新建的图片管理者中以便图片加载完毕后做缓存处理。

根据上面的代码调用PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);看出load()方法由ImageProvider对象实现,这里就是NetworkImage对象,看下其具体实现代码

@override
ImageStreamCompleter load(NetworkImage key) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key),
scale: key.scale,
informationCollector: (StringBuffer information) {
information.writeln(‘Image provider: $this’);
information.write(‘Image key: $key’);
}
);
}

代码中其就是创建一个MultiFrameImageStreamCompleter对象并返回,这是一个多帧图片管理器,表明Flutter是支持GIF图片的。创建对象时的codec变量由_loadAsync方法的返回值初始化,查看该方法内容

static final HttpClient _httpClient = HttpClient();

Future<ui.Codec> _loadAsync(NetworkImage key) async {
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)
throw Exception(‘HTTP request failed, statusCode: ${response?.statusCode}, $resolved’);

final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
if (bytes.lengthInBytes == 0)
throw Exception(‘NetworkImage is an empty file: $resolved’);

return PaintingBinding.instance.instantiateImageCodec(bytes);
}

这里才是关键,就是通过HttpClient对象对指定的url进行下载操作,下载完成后根据图片二进制数据实例化图像编解码器对象Codec,然后返回。

那么图片下载完成后是如何显示到界面上的呢,下面看下MultiFrameImageStreamCompleter的构造方法实现

MultiFrameImageStreamCompleter({
@required Future<ui.Codec> codec,
@required double scale,
InformationCollector informationCollector
}) : assert(codec != null),
_informationCollector = informationCollector,
_scale = scale,
_framesEmitted = 0,
_timer = null {
codec.then(_handleCodecReady, onError: (dynamic error, StackTrace stack) {
reportError(
context: ‘resolving an image codec’,
exception: error,
stack: stack,
informationCollector: informationCollector,
silent: true,
);
});
}

看,构造方法中的代码块,codec的异步方法执行完成后会调用_handleCodecReady函数,函数内容如下

void _handleCodecReady(ui.Codec codec) {
_codec = codec;
assert(_codec != null);

_decodeNextFrameAndSchedule();
}

方法中会将codec对象保存起来,然后解码图片帧

Future _decodeNextFrameAndSchedule() async {
try {
_nextFrame = await _codec.getNextFrame();
} catch (exception, stack) {
reportError(
context: ‘resolving an image frame’,
exception: exception,
stack: stack,
informationCollector: _informationCollector,
silent: true,
);
return;
}
if (_codec.frameCount == 1) {
// This is not an animated image, just return it and don’t schedule more
// frames.
_emitFrame(ImageInfo(image: _nextFrame.image, scale: _scale));
return;
}
SchedulerBinding.instance.scheduleFrameCallback(_handleAppFrame);
}

如果图片是png或jpg只有一帧,则执行_emitFrame函数,从帧数据中拿到图片帧对象根据缩放比例创建ImageInfo对象,然后设置显示的图片信息

void _emitFrame(ImageInfo imageInfo) {
setImage(imageInfo);
_framesEmitted += 1;
}

/// Calls all the registered listeners to notify them of a new image.
@protected
void setImage(ImageInfo image) {
_currentImage = image;
if (_listeners.isEmpty)
return;
final List localListeners = _listeners.map(
(_ImageListenerPair listenerPair) => listenerPair.listener
).toList();
for (ImageListener listener in localListeners) {
try {
listener(image, false);
} catch (exception, stack) {
reportError(
context: ‘by an image listener’,
exception: exception,
stack: stack,
);
}
}
}

这时就会根据添加的监听器来通知一个新的图片需要渲染。那么这个监听器是什么时候添加的呢,我们回头看一下**_ImageState类中的didChangeDependencies()方法内容,执行完_resolveImage();后会执行_listenToStream();**方法

void _listenToStream() {
if (_isListeningToStream)
return;
_imageStream.addListener(_handleImageChanged);
_isListeningToStream = true;
}

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数同学面临毕业设计项目选题时,很多人都会感到无从下手,尤其是对于计算机专业的学生来说,选择一个合适的题目尤为重要。因为毕业设计不仅是我们在大学四年学习的一个总结,更是展示自己能力的重要机会。

因此收集整理了一份《2024年计算机毕业设计项目大全》,初衷也很简单,就是希望能够帮助提高效率,同时减轻大家的负担。
img
img
img

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
img

存中…(img-QIG4g8d0-1712517558211)]
[外链图片转存中…(img-YL3RJQ8w-1712517558211)]

既有Java、Web、PHP、也有C、小程序、Python等项目供你选择,真正体系化!

由于项目比较多,这里只是将部分目录截图出来,每个节点里面都包含素材文档、项目源码、讲解视频

如果你觉得这些内容对你有帮助,可以添加VX:vip1024c (备注项目大全获取)
[外链图片转存中…(img-UFmO0i26-1712517558211)]

  • 12
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值