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;
}
该方法就向ImageStream对象中添加了监听器_handleImageChanged,监听方法如下
void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) {
setState(() {
_imageInfo = imageInfo;
});
}
最终就是调用setState方法来通知界面刷新,将下载到的图片渲染到界面上来了。
实际问题
从以上源码分析,我们应该清楚了整个网络图片从加载到显示的过程,不过使用这种原生的方式我们发现网络图片只是进行了内存缓存,如果杀掉应用进程再重新打开后还是要重新下载图片,这对于用户而言,每次打开应用还是会消耗下载图片的流量,不过我们可以从中学习到一些思路来自己设计网络图片加载框架,下面作者就简单的基于Image.network来进行一下改造,增加图片的磁盘缓存。
解决方案
我们通过源码分析可知,图片在缓存中未找到时,会通过网络直接下载获取,而下载的方法是在NetworkImage类中,于是我们可以参考NetworkImage来自定义一个ImageProvider。
代码实现
拷贝一份NetworkImage的代码到新建的network_image.dart文件中,在_loadAsync方法中我们加入磁盘缓存的代码。
static final CacheFileImage _cacheFileImage = CacheFileImage();
Future<ui.Codec> _loadAsync(NetworkImage key) async {
assert(key == this);
/// 新增代码块start
/// 从缓存目录中查找图片是否存在
final Uint8List cacheBytes = await _cacheFileImage.getFileBytes(key.url);
if(cacheBytes != null) {
return PaintingBinding.instance.instantiateImageCodec(cacheBytes);
}
/// 新增代码块end
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’);
/// 新增代码块start
/// 将下载的图片数据保存到指定缓存文件中
await _cacheFileImage.saveBytesToFile(key.url, bytes);
/// 新增代码块end
return PaintingBinding.instance.instantiateImageCodec(bytes);
}
代码中注释已经表明了基于原有代码新增的代码块,CacheFileImage是自己定义的文件缓存类,完整代码如下
import ‘dart:convert’;
import ‘dart:io’;
import ‘dart:typed_data’;
import ‘package:crypto/crypto.dart’;
import ‘package:path_provider/path_provider.dart’;
class CacheFileImage {
/// 获取url字符串的MD5值
static String getUrlMd5(String url) {
var content = new Utf8Encoder().convert(url);
var digest = md5.convert(content);
return digest.toString();
}
/// 获取图片缓存路径
Future getCachePath() async {
Directory dir = await getApplicationDocumentsDirectory();
Directory cachePath = Directory(“${dir.path}/imagecache/”);
if(!cachePath.existsSync()) {
cachePath.createSync();
}
return cachePath.path;
}
/// 判断是否有对应图片缓存文件存在
Future getFileBytes(String url) async {
String cacheDirPath = await getCachePath();
String urlMd5 = getUrlMd5(url);
File file = File(“
c
a
c
h
e
D
i
r
P
a
t
h
/
cacheDirPath/
cacheDirPath/urlMd5”);
print(“读取文件:${file.path}”);
if(file.existsSync()) {
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级安卓工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Android移动开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Android)
最后
如果你看到了这里,觉得文章写得不错就给个赞呗?如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足。谢谢。
最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!
需要资料的朋友可以点击我的GitHub免费领取
]
最后针对Android程序员,我这边给大家整理了一些资料,包括不限于高级UI、性能优化、移动架构师、NDK、混合式开发(ReactNative+Weex)微信小程序、Flutter等全方面的Android进阶实践技术;希望能帮助到大家,也节省大家在网上搜索资料的时间来学习,也可以分享动态给身边好友一起学习!