Flutter开发——图片加载与缓存源码解析

news2024/11/16 11:39:35

在Flutter中有个图片组件:Image,通常会使用它的Image.network(src)Image.file(src)Image.asset(src)来加载图片。
下面是Image的普通构造方法:

  const Image({
    super.key,
    required this.image,
    this.frameBuilder,
    this.loadingBuilder,
    this.errorBuilder,
    this.semanticLabel,
    this.excludeFromSemantics = false,
    this.width,
    this.height,
    this.color,
    this.opacity,
    this.colorBlendMode,
    this.fit,
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
    this.centerSlice,
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
    this.isAntiAlias = false,
    this.filterQuality = FilterQuality.low,
  })

从它的构造方法可以看出Image组件有个必传参数image,它是ImageProvider类型。ImageProvider是个抽象类,定义了图片数据获取和加载的相关接口。它的主要职责有两个:

  • 1.提供图片数据源;
  • 2.缓存图片;

ImageProvider抽象类:

abstract class ImageProvider<T extends Object> {
  const ImageProvider();
  
  ImageStream resolve(ImageConfiguration configuration) {
    ...
  }
  
  ImageStream createStream(ImageConfiguration configuration) {
    return ImageStream();
  }

  
  void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
    ...
  }
  
  Future<bool> evict({ ImageCache? cache, ImageConfiguration configuration = ImageConfiguration.empty }) async {
    ...
  }
  
  Future<T> obtainKey(ImageConfiguration configuration);
}

从上面源码中可以发现图片的加载和解析是由ImageProvider来完成的,具体说是由它的子类实现的。ImageProvider派生了很多子类,例如NetworkImage类和AssetImage类,NetworkImage是从网络来加载图片数据,而AssetImage则是从安装包里的资源文件中加载。

图片的加载

在ImageProvider中提供了一个load()方法,它是一个用于加载图片数据源的接口,不同的数据源的加载方式不同。
加载网络图片是用Image.network(),对应的ImageProvider是NetworkImage类,它实现了load()方法:

  
  ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
    return MultiFrameImageStreamCompleter(
      codec: _loadAsync(key, null, decode),
      scale: key.scale,
      debugLabel: key.file.path,
      informationCollector: () => <DiagnosticsNode>[
        ErrorDescription('Path: ${file.path}'),
      ],
    );
  }
  • load方法的返回值类型是ImageStreamCompleter,它是一个抽象类,定义了管理图片加载过程的一些接口,Image Widget中是通过它来监听图片加载状态的;
  • MultiFrameImageStreamCompleter是ImageStreamCompleter的一个子类,实现该类,可以快速的创建出一个ImageStreamCompleter实例;

MultiFrameImageSteamCompleter有个codec参数,源码中是用调用了_loadAsync()方法,方法的实现如下:

Future<ui.Codec> _loadAsync(
   NetworkImage key,
   StreamController<ImageChunkEvent> chunkEvents,
   image_provider.DecoderBufferCallback? decode,
   image_provider.DecoderCallback? decodeDepreacted,
   ) 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) {
     await response.drain<List<int>>(<int>[]);
     throw image_provider.NetworkImageLoadException(statusCode: response.statusCode, uri: resolved);
   }

   final Uint8List bytes = await consolidateHttpClientResponseBytes(
     response,
     onBytesReceived: (int cumulative, int? total) {
       chunkEvents.add(ImageChunkEvent(
         cumulativeBytesLoaded: cumulative,
         expectedTotalBytes: total,
       ));
     },
   );
   ...
   if (decode != null) {
       final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
       return decode(buffer);
     } else {
       assert(decodeDepreacted != null);
       return decodeDepreacted!(bytes);
     }
}

通过源码可以发现_loadAsync()方法负责下载图片,并调用decode()方法对下载的图片数据进行解码。

图片的缓存

图片的缓存的关键方法是:obtainKey(ImageConfiguration)
该方法主要是为了配合实现图片缓存,ImageProvider从数据源加载完数据后,会在全局的ImageCache中缓存图片数据,而图片数据缓存是一个Map,而Map的key便是调用此方法的返回值,不同的key代表不同的图片数据缓存。
resolve方法是ImageProvider暴露给Image的主入口方法,它接收一个ImageConfiguration参数,返回ImageStream。

ImageStream resolve(ImageConfiguration configuration) {
  assert(configuration != null);
  final ImageStream stream = createStream(configuration);
  _createErrorHandlerAndKey(
    configuration,
        (T key, ImageErrorListener errorHandler) {
      resolveStreamForKey(configuration, stream, key, errorHandler);
    },
       ...
  );
  return stream;
}

ImageConfiguration:包含图片和设备的相关信息。内部会调用_createErrorHandlerAndKey来加载key并创建错误处理函数。
resolveStreamForKey方法:


void resolveStreamForKey(ImageConfiguration configuration, ImageStream stream, T key, ImageErrorListener handleError) {
  if (stream.completer != null) {
    //缓存逻辑
    final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
      key,
          () => stream.completer!,
      onError: handleError,
    );
    assert(identical(completer, stream.completer));
    return;
  }
  //缓存逻辑
  final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
    key,
        () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
    onError: handleError,
  );
  if (completer != null) {
    stream.setCompleter(completer);
  }
}

在resolve方法中会调用resolveStreamForKey,其中PaintingBinding.instance.imageCache是ImageCache的一个实例,它是PaintingBinding的一个属性,并且PaintingBinding.instance和imageCache都是单例,所以图片缓存是全局的,统一由PaintingBinding.instance.imageCache来管理。

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>{};

  final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};

  int get maximumSize => _maximumSize;
  int _maximumSize = _kDefaultSize;
  ...
}

ImageCache中有三个缓存池:

  • _pendingImages:用来存放正在加载和解码的图片,当图片加载和解码完成后,ImageCache会自动移除_pendingImages相应的Entry;
  • _cache:用来存储所有加载过的图片,如果图片缓存的数量和内存占用大小没有超过ImageCache的上限,_cache就会一直保留Cache Entry,如果超过了则会按LRU进行释放;
  • _liveImages:用来存放使用中的图片,当Image Widget移除或者更换图片,或者Image Widget自身被移除,ImageCache会从_liveImages移除相应Entry;
    只有ImageCache从所有缓存池都释放了同一张图片的Entry,该图片才算在内存中真正释放。

图片缓存的Key怎么生成

因为Map中相同key的值会被覆盖,也就是说key是图片缓存的唯一标识,只要key不同,那么图片数据就会分布缓存。那么图片的唯一标识是什么呢?从源码中可以看到ImageProvider.obtainKey()方法,图片在缓存时所使用的key就是通过这个方法生成的,并且ImageProvider的子类对这个方法进行了重写。
在这里插入图片描述
这就意味着不同的ImageProvider对key的定义逻辑是不同的。NetworkImage的obtainKey()方法:

  
  Future<NetworkImage> obtainKey(image_provider.ImageConfiguration configuration) {
    return SynchronousFuture<NetworkImage>(this);
  }

创建了一个SynchronousFuture,然后将自身返回,所以对比key的时候,看操作符==就可以了:

  
  bool operator ==(Object other) {
    if (other.runtimeType != runtimeType) {
      return false;
    }
    return other is NetworkImage && other.url == url && other.scale == scale;
  }

对于网络图片来说,key实际上是url+缩放比例。所以两张图片的url和scale都相同,那么他们在内存里面就只会缓存一份。

设置缓存大小

ImageCache类中有默认的缓存大小:

const int _kDefaultSize = 1000;//最多1000张
const int _kDefaultSizeBytes = 100 << 20; //最大 100 MiB

我们也可以通过如下代码去设置自定义的缓存上限:

 PaintingBinding.instance.imageCache.maximumSize=500; //最多500张
 PaintingBinding.instance.imageCache.maximumSizeBytes = 50 << 20; //最大50M

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/676969.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

第四章 机器学习

文章目录 第四章 决策树4.1基本流程4.2划分选择4.2.1信息增益4.2.2增益率4.2.3基尼指数 4.3剪枝处理4.3.1预剪枝4.3.2后剪枝 4.4连续与缺失值4.4.1连续值处理4.4.2缺失值处理 4.5多变量决策树 第四章 决策树 4.1基本流程 决策过程&#xff1a; 基本算法&#xff1a; 4.2划…

git——使用ssh连接远程仓库

文章目录 前言一. 获取邮箱和密码1. 本地配置你的名字和邮箱2. 使用命令获取你本地的邮箱和密码 二、生成ssh公钥1.任意一个文件夹路径打开Git Bash Here并输入以下命令连按三次回车2. 根据上面红框部分的地址打开文件夹3. 打开并查看id_rsa.pub 文件 三、在GitHub上连接ssh1. …

电商API知识点整理(一)商品采集接口获取商品详情数据API

商品采集接口背景 电商商品采集接口是一种机器人软件接口&#xff0c;用于从电子商务网站上爬取商品信息。它的主要作用是将电商网站上的商品信息采集和整合&#xff0c;方便用户使用。传统的商品采集需要人工收集和整理&#xff0c;工作量大、效率低&#xff1b;而电商商品采…

Flutter的文本、图片和按钮使用

像视图数据流转机制、底层渲染方案、视图更新策略等知识&#xff0c;都是构成一个UI框架的根本&#xff0c;看似枯燥&#xff0c;却往往具有最长久的生命力。 因此&#xff0c; 只有把这些最基础的知识弄明白&#xff0c;修好内功&#xff0c;才能触类旁通&#xff0c;由点及面…

输入阻抗、输出阻抗和阻抗匹配

读者问了一个问题&#xff1a;“集总参数电路中&#xff0c;阻抗匹配&#xff08;内阻外阻&#xff09;可以使负载得到最大的功率输出”这句话怎么理解&#xff1f; 这里涉及到几个概念&#xff1a;输入阻抗、输出阻抗、阻抗匹配&#xff0c;今天简单的聊一聊。 先了解一下阻…

用Visual Studio 2022写出你第一个Windows程序(程序保证能正常运行)

我是荔园微风&#xff0c;作为一名在IT界整整25年的老兵&#xff0c;今天来看看如何用Visual C写出你第一个Windows程序。 与其看很多Windows的书&#xff0c;不如先自己动手写一个Windows程序。由于Windows程序的特有机制&#xff0c;不建议去写那种简单的HELLO WORLD&#x…

【计算机网络详解】——网络层(学习笔记)

&#x1f4d6; 前言&#xff1a;网络层它承担着网络间的数据传输和路由选择等核心任务&#xff0c;通过在传输层协议的基础上添加了路由和转发等功能&#xff0c;使得数据能够在全球范围内的互联网中自由流动。在这篇博客中&#xff0c;我们将深入探讨网络层的工作原理和具体实…

D. Binary String Sorting(枚举位置)

Problem - 1809D - Codeforces 给定一个仅由字符0和/或1组成的二进制字符串s。 您可以对此字符串执行几个操作&#xff08;可能为零&#xff09;。有两种类型的操作&#xff1a; 选择两个相邻的元素并交换它们。为了执行此操作&#xff0c;您需要支付1012硬币&#xff1b; 选…

网络作业10【计算机网络】

网络作业10【计算机网络】 前言推荐网络作业10一. 单选题&#xff08;共13题&#xff0c;68.2分&#xff09;二. 多选题&#xff08;共4题&#xff0c;21.2分&#xff09;三. 阅读理解&#xff08;共2题&#xff0c;10.6分&#xff09; 练习5-39 最后 前言 2023-6-23 15:35:39…

MySQL ----主从复制、分离解析

文章目录 一、MySQL 主从复制1.1服务性能扩展方式1.2 MySQL的扩展什么是读写分离&#xff1f; 1.3为什么要读写分离呢&#xff1f;1.4什么时候要读写分离&#xff1f;1.5主从复制与读写分离1.6mysql支持的复制类型1.7主从复制的工作过程1.8MySQL 读写分离原理1.9目前较为常见的…

2023年05月份青少年软件编程Scratch图形化等级考试试卷四级真题(含答案)

2023-05 Scratch四级真题 分数&#xff1a;100 题数&#xff1a;24 测试时长&#xff1a;90min 一、单选题(共10题&#xff0c;共30分) 1. 下列积木运行后的结果是&#xff1f;&#xff08;B&#xff09;&#xff08;说明&#xff1a;逗号后面无空格&#xff09;&#xff08…

考研算法29天:希尔排序 【希尔排序】

算法介绍 希尔排序 等差数列 普通版插入排序 循环数组 第一次每n/2为间隔分为4组&#xff0c;然后组内排序。 第二次每n/4为间隔分为2组。然后组内排序 第三次n/8为间隔分为一组。然后组内排序。 组内排序用插入排序来排序。 注&#xff1a;也可以第一次为n/3为间隔&am…

Elasticsearch:DSL Query

Query DSL的分类 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括&#xff1a; 查询所有&#xff1a;查询出所有的数据&#xff0c;一般测试用&#xff0c;例如&#xff1a;match_all&#xff0c;但有分页限制&#xff0c;一次20…

知乎网友问题:Android 悬浮窗怎么让窗口响应事件的同时,也能让背后挡住的地方收到事件?---腾讯课堂千里马亲自解答

问题&#xff1a;Android 悬浮窗怎么让窗口响应事件的同时&#xff0c;也能让背后挡住的地方收到事件&#xff1f; 点击悬浮窗&#xff0c;自己能收到事件&#xff0c;背后挡住的区域也要能收到&#xff0c;怎么实现&#xff0c;return. false 无效 原生android机制不支持原因…

【C++入门第五期】类和对象(中)

这里写目录标题 类的6个默认成员函数构造函数特征 析构函数概念特性 拷贝构造特征拷贝构造如何自定义 运算符重载赋值运算符重载赋值运算符重载前置和后置重载 取地址及const取地址操作符重载 类的6个默认成员函数 如果一个类中什么成员都没有&#xff0c;简称为空类。 class…

基于深度学习的高精度抽烟行为检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度抽烟行为检测识别系统可用于日常生活中或野外来检测与定位抽烟行为目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的抽烟行为目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5…

【五子棋实战】第6章 调用接口进行联调

【五子棋实战】第6章 调用接口进行联调 Ajax调用接口 调用五子棋接口 点击优化 尾声 更多待开发的功能 Ajax调用接口 引入Jquery&#xff0c;使用JQ封装的ajax&#xff0c;demo如下&#xff1a; <script src"jquery-3.5.0.min.js"></script> <…

无显示器玩转树莓派桌面版

title: 无显示器玩转树莓派桌面版 zhaoolee在Github开启了长篇连载《树莓派不吃灰》https://github.com/zhaoolee/pi 目前已经更新到18篇&#xff0c;主要是给树莓派刷Ubuntu当做家庭服务器用。 恰好手头还有一块闲置的树莓派4B &#xff0c;我打算深度玩一下树莓派桌面版&…

CODESYS电子齿轮同步MC_GearIn指令编程应用

MC_GearIn属于比例随动控制,有关比例随动控制详细介绍请参看下面文章: 运动控制比例随动系统_RXXW_Dor的博客-CSDN博客PLC如何测量采集编码器的位置数据,不清楚的可以参看我的另一篇博文:三菱FX3U PLC高速计数器应用(附代码)_RXXW_Dor的博客-CSDN博客本文主要以三菱FX3U…

第十一章 EfficientNetv1网络详解

系列文章目录 第一章 AlexNet网络详解 第二章 VGG网络详解 第三章 GoogLeNet网络详解 第四章 ResNet网络详解 第五章 ResNeXt网络详解 第六章 MobileNetv1网络详解 第七章 MobileNetv2网络详解 第八章 MobileNetv3网络详解 第九章 ShuffleNetv1网络详解 第十章…