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

那么图片下载完成后是如何显示到界面上的呢,下面看下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()) {
return await file.readAsBytes();
}

return null;
}

/// 将下载的图片数据缓存到指定文件
Future saveBytesToFile(String url, Uint8List bytes) 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”);
if(!file.existsSync()) {
file.createSync();
await file.writeAsBytes(bytes);
}
}
}

这样就增加了文件缓存的功能,思路很简单,就是在获取网络图片之前先检查一下本地文件缓存目录中是否有缓存文件,如果有则不用再去下载,否则去下载图片,下载完成后立即将下载到的图片缓存到文件中供下次需要时使用。

工程的pubspec.yaml中需要增加以下依赖库

dependencies:
path_provider: ^0.4.1
crypto: ^2.0.6

自定义ImageProvider使用

在创建图片Widget时使用带参数的非命名构造函数,指定image参数为自定义ImageProvider对象即可,代码示例如下

import ‘imageloader/network_image.dart’ as network;

Widget getNetworkImage() {
return Container(

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

深知大多数Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新

如果你觉得这些内容对你有帮助,可以添加V获取:vip204888 (备注Android)
img

学习分享

在当下这个信息共享的时代,很多资源都可以在网络上找到,只取决于你愿不愿意找或是找的方法对不对了

很多朋友不是没有资料,大多都是有几十上百个G,但是杂乱无章,不知道怎么看从哪看起,甚至是看后就忘

如果大家觉得自己在网上找的资料非常杂乱、不成体系的话,我也分享一套给大家,比较系统,我平常自己也会经常研读。

2021最新上万页的大厂面试真题

image

七大模块学习资料:如NDK模块开发、Android框架体系架构…

image

2021大厂面试真题:

image

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
img
图片转存中…(img-9WCREhez-1712719808464)]

只有系统,有方向的学习,才能在短时间内迅速提高自己的技术,只有不断地学习,不懈的努力才能拥有更好的技术,才能在互联网行业中立于不败之地。

一个人可以走的很快,但一群人才能走的更远。不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎扫码加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
[外链图片转存中…(img-fZNaOD99-1712719808464)]

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值