Flutter 图片加载

6、FadeInImage.memoryNetwork
默认占位图和淡入效果

import ‘package:transparent_image/transparent_image.dart’;

FadeInImage.memoryNetwork(
placeholder: kTransparentImage, //kTransparentImage 属于 transparent_image 库
image: ‘https://p0.ssl.qhimg.com/t0183421f63f84fccaf.gif’,
);

7、Icon Icons 图片参考URL

new Icon(Icons.android,size: 200,);

Flutter 加载 images 的分辨率

Flutter 可以为当前设备加载适合其分辨率的图像。指定不同素设备像比例的图片可以这样分配asset文件夹:

  • …icon/happy.png
  • …/2.0x/happy.png
  • …/3.0x/happy.png

主资源默认对应于 1.0 倍的分辨率图片;在设备像素比率为 1.8 的设备上会选用 .../2.0x/happy.png ;对于在像素比率 2.7 的设备上 ,会选用 .../3.0x/happy.png

pubspec.yaml 中 asset 声明中每一项都标识与实际文件对应。但是主资源缺少时,会按分辨率从低到高的顺序寻找加载。这里的加载方案,可以参考 Android 系统中图片加载的逻辑作对比。

Flutter 打包应用时,资源会按照 key-value 的形式存入 apk 的 assets/flutter_assets/AssetManifest.json 文件中,加载资源时先解析 json 文件,选择最适合的图片进行加载显示,其中 AssetManifest.json 的具体内容简介如:

{
“assets/happy.png”:[
“assets/2.0x/happy.png”,
“assets/3.0x/happy.png”
]
}

Android

android 上可以通过 AssetManager 获取 asset, 根据 key 查找到 openFd 。

key 是由 PluginRegistry.Registrar 的 lookupKeyForAsset 与 FlutterView 的 getLookupKeyForAsset 得到;

PluginRegistry.Registrar 用于开发插件,而 FlutterView 则用于开发平台 app 的 view。

pubspec.yaml

flutter:
assets:

  • icons/happy.png
Java plugin code

AssetManager assetManager = registrar.context().getAssets();
String key = registrar.lookupKeyForAsset(“icons/happy.png”);
AssetFileDescriptor fd = assetManager.openFd(key);

iOS

iOS 开发使用 mainbundle 获取 assets。

使用 FlutterPluginRegistrar 的 lookupKeyForAsset 和 lookupKeyForAsset:fromPackage: 方法获取文件路径 ;FlutterViewController 的 lookupKeyForAsset 和lookupKeyForAsset:fromPackage: 方法获取文件路径 ;

然后 FlutterPluginRegistrar 用于开发插件,而 FlutterViewController 则用于开发平台 app 的 view 。

Objective-C plugin

NSString* key = [registrar lookupKeyForAsset:@“icons/happy.png”];
NSString* path = [[NSBundle mainBundle] pathForResource:key ofType:nil];

当然 pubspec.yaml 配置都是一致的。

源码分析

图片加载方式中有四种方式,接下来我们一起看看 framework 层加载图片是如何实现的。我们就以 Image.network 为例,跟进一下相关源码实现。

Image.network 的方法如下:

Image.network(
String src, {
Key key,
double scale = 1.0,
this.frameBuilder,
this.loadingBuilder,
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,
Map<String, String> headers,
}) : image = NetworkImage(src, scale: scale, headers: headers),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null),
super(key: key);

这方法的作用就是创建一个 用于显示从网络得到的 ImageStream 的 image 小部件,加载网络图片的 image 是由 NetworkImage 创建出来的,其中参数 src, scale, headers 是不能为空的,其他的参数可以不做要求。NetworkImage 又是继承自 ImageProvider,所以 image 就是 ImageProvider 。ImageProvider 是个抽象类,它的实现类包括:NetworkImage、FileImage、ExactAssetImage、AssetImage、MemoryImage、AssetBundleImageProvider。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

Image 源码部分如下:

class Image extends StatefulWidget {
/// 用于显示的 image
final ImageProvider image;

@override
_ImageState createState() => _ImageState();
}

_ImageState 类

class _ImageState extends State with WidgetsBindingObserver {
ImageStream _imageStream;
ImageInfo _imageInfo;

@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}

@override
void didChangeDependencies() {
_updateInvertColors();
_resolveImage();//解析图片从这里开始
//设置和移除监听图片变化的回调
if (TickerMode.of(context))
_listenToStream();
else
_stopListeningToStream();
super.didChangeDependencies();
}

void _resolveImage() {
//根据 ImageConfiguration 调用 ImageProvider 的 resolve 函数获得 ImageStream 对象
final ImageStream newStream = widget.image.resolve(createLocalImageConfiguration(
context,
size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
));
_updateSourceStream(newStream);
}

}

它的生命周期方法方法包括initState()didChangeDependencies()build()deactivate()dispose()didUpdateWidget() 等等。当它插入到渲染树时,先调用initState()函数,再调用 didChangeDependencies()。代码中可以看到调用了方法 _resolveImage(),这个方法中创建了 ImageStream 的新对象 newStream 。widget.image 就是 ImageProvider,调用resolve方法,代码如下:

ImageStream resolve(ImageConfiguration configuration) {
final ImageStream stream = ImageStream();
T obtainedKey;
bool didError = false;
Future handleError(dynamic exception, StackTrace stack) async {
if (didError) {
return;
}
didError = true;
await null; // 等待事件轮询,以防侦听器被添加到图像流中。

final _ErrorImageCompleter imageCompleter = _ErrorImageCompleter();
stream.setCompleter(imageCompleter);

}

Future key;
try {
key = obtainKey(configuration);
} catch (error, stackTrace) {
return;
}
key.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;

ImageStreamCompleter 用于管理 dart:ui 加载的类的基类。ImageStreams 的对象很少直接构造,而是由 ImageStreamCompleter 自动配置它。ImageStream 中的图片管理者 ImageStreamCompleter 通过方法创建,imageCache 是 Flutter 框架中实现的用于图片缓存的单例,它这 Dart 虚拟机加载时就已经创建。imageCache 最多可缓存 1000 张图像和 100MB 内存空间。可以使用 [maximumSize] 和 [maximumSizeBytes]调整最大大小。

PaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key), onError: handleError);

根据源码可以看到两个关键方法 :putIfAbsent 和 load。

putIfAbsent

ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader(), {ImageErrorListener onError }) {
ImageStreamCompleter result = _pendingImages[key]?.completer;
// 因为图像还没有加载,不需要做任何事情。
if (result != null)
return result;
// 从缓存列表中根据Key删除对应的 imageprovider,便于将它移动到下面最近使用位置。
final _CachedImage image = _cache.remove(key);
if (image != null) {
_cache[key] = image;
return image.completer;
}
try {
result = loader();
} catch (error, stackTrace) {

}
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);
// 如果图像大于最大缓存大小,且缓存大小不为零,则将缓存大小增加到图像大小加上 1000。
// 思考点:一直这么加什么时候引起崩溃?
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);
// 移除 [_PendingImage.removeListener] 上的监听
result.addListener(streamListener);
}
return result;
}

load

/// 拉取网络图片的 image_provider.NetworkImage 具体实现.
class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkImage> implements image_provider.NetworkImage {

@override
ImageStreamCompleter load(image_provider.NetworkImage key) {

final StreamController chunkEvents = StreamController();

return MultiFrameImageStreamCompleter(

codec: _loadAsync(key, chunkEvents),

chunkEvents: chunkEvents.stream,
scale: key.scale,
informationCollector: () {
return [
DiagnosticsProperty<image_provider.ImageProvider>(‘Image provider’, this),
DiagnosticsProperty<image_provider.NetworkImage>(‘Image key’, key),
];
},
);
}

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 request.close();
if (response.statusCode != HttpStatus.ok)
throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
//将网络返回的 response 信息,转换成内存中的 Uint8List bytes。这里面有解压 gzip 的逻辑。
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();
}
}

将网络返回的response信息,转换成内存中的 Uint8List bytes,最终返回一个实例化图像编解码器对象Codec,此处 Codec 可以移步到 painting.dart 文件的 _instantiateImageCodec 看出来它是调用了native方法去处理了。

MultiFrameImageStreamCompleter

这个对象就是 ImageStreamCompleter 的具体实现,见名知意,多帧图片流管理,作用管理图像帧的解码和调度。

这个类处理两种类型的帧:

  • 图像帧 :动画图像的图像帧。

  • app 帧 :Flutter 引擎绘制到屏幕的帧,显示到应用程序 GUI。

这就不贴所有代码了,在 image_stream.dart 文件中 可见 class MultiFrameImageStreamCompleter。

MultiFrameImageStreamCompleter({
@required Future<ui.Codec> codec,
@required double scale,
Stream chunkEvents,
InformationCollector informationCollector,
}) : assert(codec != null),
_informationCollector = informationCollector,
_scale = scale {
codec.then(_handleCodecReady, onError: (dynamic error, StackTrace stack) {

});

_handleCodecReady

这里 codec 异步回调次方法

void _handleCodecReady(ui.Codec codec) {
_codec = codec;
if (hasListeners) {
_decodeNextFrameAndSchedule();
}
}

最后

总而言之,Android开发行业变化太快,作为技术人员就要保持终生学习的态度,让学习力成为核心竞争力,所谓“活到老学到老”只有不断的学习,不断的提升自己,才能跟紧行业的步伐,才能不被时代所淘汰。

在这里我分享一份自己收录整理上述技术体系图相关的几十套腾讯、头条、阿里、美团等公司20年的面试题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
题,把技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,这里以图片的形式给大家展示一部分。

[外链图片转存中…(img-MSKWJEW8-1714811829269)]

[外链图片转存中…(img-Y5KCUzxv-1714811829270)]

[外链图片转存中…(img-iQGcJvqd-1714811829271)]

还有高级架构技术进阶脑图、Android开发面试专题资料,高级进阶架构资料 帮助大家学习提升进阶,也节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

  • 16
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值