/// * 0 表示只执行一次
/// * -1 表示循环执行
int get repetitionCount native ‘Codec_repetitionCount’;
/// 获取下一个动画帧
Future getNextFrame() {
return _futurize(_getNextFrame);
}
String _getNextFrame(_Callback callback) native ‘Codec_getNextFrame’;
我们可以看到Codec
最终的结果是一个或多个(动图)帧,而这些帧最终会绘制到屏幕上。
MultiFrameImageStreamCompleter 的
codec
参数值为_loadAsync
方法的返回值,我们继续看_loadAsync
方法的实现:
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController chunkEvents,
) async {
try {
//下载图片
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 req 《Android学习笔记总结+最新移动架构视频+大厂安卓面试真题+项目实战源码讲义》无偿开源 徽信搜索公众号【编程进阶路】 uest.close();
if (response.statusCode != HttpStatus.ok)
throw Exception(…);
// 接收图片数据
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 PaintingBinding.instance.instantiateImageCodec(bytes);
} finally {
chunkEvents.close();
}
}
可以看到_loadAsync
方法主要做了两件事:
- 下载图片。
- 对下载的图片数据进行解码。
下载逻辑比较简单:通过HttpClient
从网上下载图片,另外下载请求会设置一些自定义的header,开发者可以通过NetworkImage
的headers
命名参数来传递。
在图片下载完成后调用了PaintingBinding.instance.instantiateImageCodec(bytes)
对图片进行解码,值得注意的是instantiateImageCodec(...)
也是一个Native API的包装,实际上会调用Flutter engine的instantiateImageCodec
方法,源码如下:
String _instantiateImageCodec(Uint8List list, _Callback callback, _ImageInfo imageInfo, int targetWidth, int targetHeight)
native ‘instantiateImageCodec’;
obtainKey(ImageConfiguration)
方法
该接口主要是为了配合实现图片缓存,ImageProvider
从数据源加载完数据后,会在全局的ImageCache
中缓存图片数据,而图片数据缓存是一个Map,而Map的key便是调用此方法的返回值,不同的key代表不同的图片数据缓存。
resolve(ImageConfiguration)
方法
resolve
方法是ImageProvider
的暴露的给Image
的主入口方法,它接受一个ImageConfiguration
参数,返回ImageStream
,即图片数据流。我们重点看一下resolve
执行流程:
ImageStream resolve(ImageConfiguration configuration) {
… //省略无关代码
final ImageStream stream = ImageStream();
T obtainedKey; //
//定义错误处理函数
Future handleError(dynamic exception, StackTrace stack) async {
… //省略无关代码
stream.setCompleter(imageCompleter);
imageCompleter.setError(…);
}
// 创建一个新Zone,主要是为了当发生错误时不会干扰MainZone
final Zone dangerZone = Zone.current.fork(…);
dangerZone.runGuarded(() {
Future key;
// 先验证是否已经有缓存
try {
// 生成缓存key,后面会根据此key来检测是否有缓存
key = obtainKey(configuration);
} catch (error, stackTrace) {
handleError(error, stackTrace);
return;
}
key.then((T key) {
obtainedKey = key;
// 缓存的处理逻辑在这里,记为A,下面详细介绍
final ImageStreamCompleter completer = PaintingBinding.instance
.imageCache.putIfAbsent(key, () => load(key), onError: handleError);
if (completer != null) {
stream.setCompleter(completer);
}
}).catchError(handleError);
});
return stream;
}
ImageConfiguration
包含图片和设备的相关信息,如图片的大小、所在的AssetBundle
(只有打到安装包的图片存在)以及当前的设备平台、devicePixelRatio(设备像素比等)。Flutter SDK提供了一个便捷函数createLocalImageConfiguration
来创建ImageConfiguration
对象:
ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) {
return ImageConfiguration(
bundle: DefaultAssetBundle.of(context),
devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0,
locale: Localizations.localeOf(context, nullOk: true),
textDirection: Directionality.of(context),
size: size,
platform: defaultTargetPlatform,
);
}
我们可以发现这些信息基本都是通过Context
来获取。
上面代码A处就是处理缓存的主要代码,这里的PaintingBinding.instance.imageCache
是 ImageCache
的一个实例,它是PaintingBinding
的一个属性,而Flutter框架中的PaintingBinding.instance
是一个单例,imageCache
事实上也是一个单例,也就是说图片缓存是全局的,统一由PaintingBinding.instance.imageCache
来管理。
下面我们看看ImageCache
类定义:
const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
class ImageCache {
// 正在加载中的图片队列
final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
// 缓存队列
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
// 缓存数量上限(1000)
int _maximumSize = _kDefaultSize;
// 缓存容量上限 (100 MB)
int _maximumSizeBytes = _kDefaultSizeBytes;
// 缓存上限设置的setter
set maximumSize(int value) {…}
set maximumSizeBytes(int value) {…}
… // 省略部分定义
// 清除所有缓存
void clear() {
// …省略具体实现代码
}
// 清除指定key对应的图片缓存
bool evict(Object key) {
// …省略具体实现代码
}
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), { ImageErrorListener onError }) {
assert(key != null);
assert(loader != null);
ImageStreamCompleter result = _pendingImages[key]?.completer;
// 图片还未加载成功,直接返回
if (result != null)
return result;
// 有缓存,继续往下走
// 先移除缓存,后再添加,可以让最新使用过的缓存在_map中的位置更近一些,清理时会LRU来清除
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) {
final int imageSize = info?.image == null ? 0 : info.image.height * info.image.width * 4;
final _CachedImage image = _CachedImage(result, imageSize);
// 下面是缓存处理的逻辑
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) {
final ImageStreamListener streamListener = ImageStreamListener(listener);
_pendingImages[key] = _PendingImage(result, streamListener);
// Listener is removed in [_PendingImage.removeListener].
result.addListener(streamListener);
}
return result;
}
// 当缓存数量超过最大值或缓存的大小超过最大缓存容量,会调用此方法清理到缓存上限以内
void _checkCacheSize() {
while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
final Object key = _cache.keys.first;
final _CachedImage image = _cache[key];
_currentSizeBytes -= image.sizeBytes;
_cache.remove(key);
}
… //省略无关代码
}
}