Flutter加载图片流程之ImageCache源码示例解析

目录
  • ImageCache
  • _pendingImages、_cache、_liveImages
  • maximumSize、currentSize
  • clear
  • evict
  • _touch
  • _checkCacheSize
  • _trackLiveImage
  • putIfAbsent
  • clearLiveImages
  • 答疑解惑

ImageCache

const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MiB
/// Class for caching images.
///
/// Implements a least-recently-used cache of up to 1000 images, and up to 100
/// MB. The maximum size can be adjusted using [maximumSize] and
/// [maximumSizeBytes].
///
/// The cache also holds a list of 'live' references. An image is considered
/// live if its [ImageStreamCompleter]'s listener count has never dropped to
/// zero after adding at least one listener. The cache uses
/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] to determine when
/// this has happened.
///
/// The [putIfAbsent] method is the main entry-point to the cache API. It
/// returns the previously cached [ImageStreamCompleter] for the given key, if
/// available; if not, it calls the given callback to obtain it first. In either
/// case, the key is moved to the 'most recently used' position.
///
/// A caller can determine whether an image is already in the cache by using
/// [containsKey], which will return true if the image is tracked by the cache
/// in a pending or completed state. More fine grained information is available
/// by using the [statusForKey] method.
///
/// Generally this class is not used directly. The [ImageProvider] class and its
/// subclasses automatically handle the caching of images.
///
/// A shared instance of this cache is retained by [PaintingBinding] and can be
/// obtained via the [imageCache] top-level property in the [painting] library.
///
/// {@tool snippet}
///
/// This sample shows how to supply your own caching logic and replace the
/// global [imageCache] variable.

ImageCache类是一个用于缓存图像的类。它实现了一个最近最少使用的缓存,最多缓存1000个图像,最大缓存100MB。缓存的最大大小可以使用maximumSize和maximumSizeBytes进行调整。

ImageCache还持有一个"活"图像引用的列表。当ImageStreamCompleter的监听计数在添加至少一个监听器后从未降至零时,图像被认为是"活"的。缓存使用ImageStreamCompleter.addOnLastListenerRemovedCallback方法来确定这种情况是否发生。

putIfAbsent方法是缓存API的主要入口点。如果给定键的先前缓存中有ImageStreamCompleter可用,则返回该实例;否则,它将首先调用给定的回调函数来获取该实例。在任何情况下,键都会被移到"最近使用"的位置。

调用者可以使用containsKey方法确定图像是否已经在缓存中。如果图像以待处理或已处理状态被缓存,containsKey将返回true。使用statusForKey方法可以获得更细粒度的信息。

通常情况下,这个类不会直接使用。ImageProvider类及其子类会自动处理图像缓存。

在PaintingBinding中保留了这个缓存的共享实例,并可以通过painting库中的imageCache顶级属性获取。

_pendingImages、_cache、_liveImages

final Map<Object, _PendingImage> _pendingImages = <Object, _PendingImage>{};
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
/// ImageStreamCompleters with at least one listener. These images may or may
/// not fit into the _pendingImages or _cache objects.
///
/// Unlike _cache, the [_CachedImage] for this may have a null byte size.
final Map<Object, _LiveImage> _liveImages = <Object, _LiveImage>{};

这段代码定义了三个 Map 对象来实现图片的缓存机制。其中:

  • _pendingImages:用于缓存正在加载中的图片,键为图片的标识符,值为 _PendingImage 对象。
  • _cache:用于缓存已经加载完成的图片,键为图片的标识符,值为 _CachedImage 对象。
  • _liveImages:用于缓存当前正在使用中的图片,即其对应的 ImageStreamCompleter 对象有至少一个监听器。键为图片的标识符,值为 _LiveImage 对象。

需要注意的是,_liveImages 中的 _LiveImage 对象的 byteSize 可能为 null,而 _cache 中的 _CachedImage 对象的 byteSize 总是非空的。这是因为 _cache 中的图片已经加载完成并占用了内存,而 _liveImages 中的图片可能还处于加载中或者被释放了内存,因此其大小可能还未确定或者已经变为 null

maximumSize、currentSize

/// Maximum number of entries to store in the cache.
///
/// Once this many entries have been cached, the least-recently-used entry is
/// evicted when adding a new entry.
int get maximumSize => _maximumSize;
int _maximumSize = _kDefaultSize;
/// Changes the maximum cache size.
///
/// If the new size is smaller than the current number of elements, the
/// extraneous elements are evicted immediately. Setting this to zero and then
/// returning it to its original value will therefore immediately clear the
/// cache.
set maximumSize(int value) {
  assert(value != null);
  assert(value >= 0);
  if (value == maximumSize) {
    return;
  }
  TimelineTask? timelineTask;
  if (!kReleaseMode) {
    timelineTask = TimelineTask()..start(
      'ImageCache.setMaximumSize',
      arguments: <String, dynamic>{'value': value},
    );
  }
  _maximumSize = value;
  if (maximumSize == 0) {
    clear();
  } else {
    _checkCacheSize(timelineTask);
  }
  if (!kReleaseMode) {
    timelineTask!.finish();
  }
}
/// The current number of cached entries.
int get currentSize => _cache.length;
/// Maximum size of entries to store in the cache in bytes.
///
/// Once more than this amount of bytes have been cached, the
/// least-recently-used entry is evicted until there are fewer than the
/// maximum bytes.
int get maximumSizeBytes => _maximumSizeBytes;
int _maximumSizeBytes = _kDefaultSizeBytes;
/// Changes the maximum cache bytes.
///
/// If the new size is smaller than the current size in bytes, the
/// extraneous elements are evicted immediately. Setting this to zero and then
/// returning it to its original value will therefore immediately clear the
/// cache.
set maximumSizeBytes(int value) {
  assert(value != null);
  assert(value >= 0);
  if (value == _maximumSizeBytes) {
    return;
  }
  TimelineTask? timelineTask;
  if (!kReleaseMode) {
    timelineTask = TimelineTask()..start(
      'ImageCache.setMaximumSizeBytes',
      arguments: <String, dynamic>{'value': value},
    );
  }
  _maximumSizeBytes = value;
  if (_maximumSizeBytes == 0) {
    clear();
  } else {
    _checkCacheSize(timelineTask);
  }
  if (!kReleaseMode) {
    timelineTask!.finish();
  }
}
/// The current size of cached entries in bytes.
int get currentSizeBytes => _currentSizeBytes;
int _currentSizeBytes = 0;

maximumSizemaximumSizeBytes用于控制缓存的大小。maximumSize是缓存中最多可以存储的元素数,超出该限制时,添加新的元素会导致最近最少使用的元素被清除。maximumSizeBytes是缓存中最多可以存储的字节数,超出该限制时,添加新元素会导致最近最少使用的元素被清除,直到缓存中元素的字节总数小于最大值。currentSizecurrentSizeBytes分别表示当前缓存中元素的数量和字节总数。在缓存大小发生变化时,会自动清除多出的元素。

clear

/// Evicts all pending and keepAlive entries from the cache.
///
/// This is useful if, for instance, the root asset bundle has been updated
/// and therefore new images must be obtained.
///
/// Images which have not finished loading yet will not be removed from the
/// cache, and when they complete they will be inserted as normal.
///
/// This method does not clear live references to images, since clearing those
/// would not reduce memory pressure. Such images still have listeners in the
/// application code, and will still remain resident in memory.
///
/// To clear live references, use [clearLiveImages].
void clear() {
  if (!kReleaseMode) {
    Timeline.instantSync(
      'ImageCache.clear',
      arguments: <String, dynamic>{
        'pendingImages': _pendingImages.length,
        'keepAliveImages': _cache.length,
        'liveImages': _liveImages.length,
        'currentSizeInBytes': _currentSizeBytes,
      },
    );
  }
  for (final _CachedImage image in _cache.values) {
    image.dispose();
  }
  _cache.clear();
  for (final _PendingImage pendingImage in _pendingImages.values) {
    pendingImage.removeListener();
  }
  _pendingImages.clear();
  _currentSizeBytes = 0;
}

clear()方法用于清除ImageCache中所有的待定和保持活动状态的图像缓存。这对于需要获取新图像的情况非常有用,例如根资产包已更新。尚未完成加载的图像不会从缓存中删除,当它们完成时,它们将像往常一样被插入。

此方法不清除图像的活动引用,因为这样做不会减少内存压力。这些图像仍然在应用程序代码中具有侦听器,并且仍将保留在内存中。如果要清除活动引用,请使用clearLiveImages()方法。

evict

/// Evicts a single entry from the cache, returning true if successful.
///
/// Pending images waiting for completion are removed as well, returning true
/// if successful. When a pending image is removed the listener on it is
/// removed as well to prevent it from adding itself to the cache if it
/// eventually completes.
///
/// If this method removes a pending image, it will also remove
/// the corresponding live tracking of the image, since it is no longer clear
/// if the image will ever complete or have any listeners, and failing to
/// remove the live reference could leave the cache in a state where all
/// subsequent calls to [putIfAbsent] will return an [ImageStreamCompleter]
/// that will never complete.
///
/// If this method removes a completed image, it will _not_ remove the live
/// reference to the image, which will only be cleared when the listener
/// count on the completer drops to zero. To clear live image references,
/// whether completed or not, use [clearLiveImages].
///
/// The `key` must be equal to an object used to cache an image in
/// [ImageCache.putIfAbsent].
///
/// If the key is not immediately available, as is common, consider using
/// [ImageProvider.evict] to call this method indirectly instead.
///
/// The `includeLive` argument determines whether images that still have
/// listeners in the tree should be evicted as well. This parameter should be
/// set to true in cases where the image may be corrupted and needs to be
/// completely discarded by the cache. It should be set to false when calls
/// to evict are trying to relieve memory pressure, since an image with a
/// listener will not actually be evicted from memory, and subsequent attempts
/// to load it will end up allocating more memory for the image again. The
/// argument must not be null.
///
/// See also:
///
///  * [ImageProvider], for providing images to the [Image] widget.
bool evict(Object key, { bool includeLive = true }) {
  assert(includeLive != null);
  if (includeLive) {
    // Remove from live images - the cache will not be able to mark
    // it as complete, and it might be getting evicted because it
    // will never complete, e.g. it was loaded in a FakeAsync zone.
    // In such a case, we need to make sure subsequent calls to
    // putIfAbsent don't return this image that may never complete.
    final _LiveImage? image = _liveImages.remove(key);
    image?.dispose();
  }
  final _PendingImage? pendingImage = _pendingImages.remove(key);
  if (pendingImage != null) {
    if (!kReleaseMode) {
      Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
        'type': 'pending',
      });
    }
    pendingImage.removeListener();
    return true;
  }
  final _CachedImage? image = _cache.remove(key);
  if (image != null) {
    if (!kReleaseMode) {
      Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
        'type': 'keepAlive',
        'sizeInBytes': image.sizeBytes,
      });
    }
    _currentSizeBytes -= image.sizeBytes!;
    image.dispose();
    return true;
  }
  if (!kReleaseMode) {
    Timeline.instantSync('ImageCache.evict', arguments: <String, dynamic>{
      'type': 'miss',
    });
  }
  return false;
}

从缓存中删除一个条目,如果成功则返回true。

同时删除等待完成的待处理图像,如果成功则返回true。当删除待处理的图像时,也将移除其上的监听器,以防止其在最终完成后添加到缓存中。

如果此方法删除了待处理的图像,则它还将删除图像的相应实时跟踪,因为此时无法确定图像是否会完成或具有任何侦听器,并且未删除实时引用可能会使缓存处于状态,其中所有后续对 [putIfAbsent] 的调用都将返回一个永远不会完成的 [ImageStreamCompleter]。

如果此方法删除了已完成的图像,则不会删除图像的实时引用,只有在监听器计数为零时才会清除实时图像引用。要清除已完成或未完成的实时图像引用,请使用 [clearLiveImages]。

key 必须等于用于在 [ImageCache.putIfAbsent] 中缓存图像的对象。

如果 key 不是立即可用的对象(这很常见),请考虑使用 [ImageProvider.evict] 间接调用此方法。

includeLive 参数确定是否也应将仍具有树中侦听器的图像清除。在图像可能损坏并需要完全丢弃缓存的情况下,应将此参数设置为true。在尝试缓解内存压力的情况下,应将其设置为false,因为具有侦听器的图像实际上不会从内存中清除,后续尝试加载它将再次为图像分配更多内存。该参数不能为空。

_touch

/// Updates the least recently used image cache with this image, if it is
/// less than the [maximumSizeBytes] of this cache.
///
/// Resizes the cache as appropriate to maintain the constraints of
/// [maximumSize] and [maximumSizeBytes].
void _touch(Object key, _CachedImage image, TimelineTask? timelineTask) {
  assert(timelineTask != null);
  if (image.sizeBytes != null && image.sizeBytes! <= maximumSizeBytes && maximumSize > 0) {
    _currentSizeBytes += image.sizeBytes!;
    _cache[key] = image;
    _checkCacheSize(timelineTask);
  } else {
    image.dispose();
  }
}

如果图片的大小小于此缓存的 [maximumSizeBytes],则使用此图像更新最近最少使用的图像缓存。

调整缓存大小以满足 [maximumSize] 和 [maximumSizeBytes] 的约束。

_checkCacheSize

// Remove images from the cache until both the length and bytes are below
// maximum, or the cache is empty.
void _checkCacheSize(TimelineTask? timelineTask) {
  final Map<String, dynamic> finishArgs = <String, dynamic>{};
  TimelineTask? checkCacheTask;
  if (!kReleaseMode) {
    checkCacheTask = TimelineTask(parent: timelineTask)..start('checkCacheSize');
    finishArgs['evictedKeys'] = <String>[];
    finishArgs['currentSize'] = currentSize;
    finishArgs['currentSizeBytes'] = currentSizeBytes;
  }
  while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
    final Object key = _cache.keys.first;
    final _CachedImage image = _cache[key]!;
    _currentSizeBytes -= image.sizeBytes!;
    image.dispose();
    _cache.remove(key);
    if (!kReleaseMode) {
      (finishArgs['evictedKeys'] as List<String>).add(key.toString());
    }
  }
  if (!kReleaseMode) {
    finishArgs['endSize'] = currentSize;
    finishArgs['endSizeBytes'] = currentSizeBytes;
    checkCacheTask!.finish(arguments: finishArgs);
  }
  assert(_currentSizeBytes >= 0);
  assert(_cache.length <= maximumSize);
  assert(_currentSizeBytes <= maximumSizeBytes);
}

这段代码实现了检查缓存大小的逻辑,用于在缓存大小超过最大限制时从缓存中移除图像以释放内存。

该方法首先创建一个空的字典对象 finishArgs 用于保存一些统计数据,然后在非生产环境下创建一个时间线任务 checkCacheTask,用于记录缓存检查的时间。如果检查任务存在,则将 evictedKeyscurrentSize 和 currentSizeBytes 添加到 finishArgs 中。

然后,使用 while 循环,当缓存大小超过最大限制时,从 _cache 字典中删除第一个元素,并释放相关图像的内存。如果 checkCacheTask 存在,则将已删除的元素的键添加到 evictedKeys 列表中。

当循环结束时,将 endSize 和 endSizeBytes 添加到 finishArgs 中,表示缓存检查后的当前大小和字节数。最后,如果 checkCacheTask 存在,则完成任务并将 finishArgs 作为参数传递。

最后,这个方法断言 _currentSizeBytes 必须大于等于零, _cache 的长度必须小于等于 maximumSize, _currentSizeBytes 必须小于等于 maximumSizeBytes

_trackLiveImage

void _trackLiveImage(Object key, ImageStreamCompleter completer, int? sizeBytes) {
  // Avoid adding unnecessary callbacks to the completer.
  _liveImages.putIfAbsent(key, () {
    // Even if no callers to ImageProvider.resolve have listened to the stream,
    // the cache is listening to the stream and will remove itself once the
    // image completes to move it from pending to keepAlive.
    // Even if the cache size is 0, we still add this tracker, which will add
    // a keep alive handle to the stream.
    return _LiveImage(
      completer,
      () {
        _liveImages.remove(key);
      },
    );
  }).sizeBytes ??= sizeBytes;
}

putIfAbsent

/// Returns the previously cached [ImageStream] for the given key, if available;
/// if not, calls the given callback to obtain it first. In either case, the
/// key is moved to the 'most recently used' position.
///
/// The arguments must not be null. The `loader` cannot return null.
///
/// In the event that the loader throws an exception, it will be caught only if
/// `onError` is also provided. When an exception is caught resolving an image,
/// no completers are cached and `null` is returned instead of a new
/// completer.
ImageStreamCompleter? putIfAbsent(Object key, ImageStreamCompleter Function() loader, { ImageErrorListener? onError }) {
  assert(key != null);
  assert(loader != null);
  TimelineTask? timelineTask;
  TimelineTask? listenerTask;
  if (!kReleaseMode) {
    timelineTask = TimelineTask()..start(
      'ImageCache.putIfAbsent',
      arguments: <String, dynamic>{
        'key': key.toString(),
      },
    );
  }
  ImageStreamCompleter? result = _pendingImages[key]?.completer;
  // Nothing needs to be done because the image hasn't loaded yet.
  if (result != null) {
    if (!kReleaseMode) {
      timelineTask!.finish(arguments: <String, dynamic>{'result': 'pending'});
    }
    return result;
  }
  // Remove the provider from the list so that we can move it to the
  // recently used position below.
  // Don't use _touch here, which would trigger a check on cache size that is
  // not needed since this is just moving an existing cache entry to the head.
  final _CachedImage? image = _cache.remove(key);
  if (image != null) {
    if (!kReleaseMode) {
      timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
    }
    // The image might have been keptAlive but had no listeners (so not live).
    // Make sure the cache starts tracking it as live again.
    _trackLiveImage(
      key,
      image.completer,
      image.sizeBytes,
    );
    _cache[key] = image;
    return image.completer;
  }
  final _LiveImage? liveImage = _liveImages[key];
  if (liveImage != null) {
    _touch(
      key,
      _CachedImage(
        liveImage.completer,
        sizeBytes: liveImage.sizeBytes,
      ),
      timelineTask,
    );
    if (!kReleaseMode) {
      timelineTask!.finish(arguments: <String, dynamic>{'result': 'keepAlive'});
    }
    return liveImage.completer;
  }
  try {
    result = loader();
    _trackLiveImage(key, result, null);
  } catch (error, stackTrace) {
    if (!kReleaseMode) {
      timelineTask!.finish(arguments: <String, dynamic>{
        'result': 'error',
        'error': error.toString(),
        'stackTrace': stackTrace.toString(),
      });
    }
    if (onError != null) {
      onError(error, stackTrace);
      return null;
    } else {
      rethrow;
    }
  }
  if (!kReleaseMode) {
    listenerTask = TimelineTask(parent: timelineTask)..start('listener');
  }
  // A multi-frame provider may call the listener more than once. We need do make
  // sure that some cleanup works won't run multiple times, such as finishing the
  // tracing task or removing the listeners
  bool listenedOnce = false;
  // We shouldn't use the _pendingImages map if the cache is disabled, but we
  // will have to listen to the image at least once so we don't leak it in
  // the live image tracking.
  final bool trackPendingImage = maximumSize > 0 && maximumSizeBytes > 0;
  late _PendingImage pendingImage;
  void listener(ImageInfo? info, bool syncCall) {
    int? sizeBytes;
    if (info != null) {
      sizeBytes = info.sizeBytes;
      info.dispose();
    }
    final _CachedImage image = _CachedImage(
      result!,
      sizeBytes: sizeBytes,
    );
    _trackLiveImage(key, result, sizeBytes);
    // Only touch if the cache was enabled when resolve was initially called.
    if (trackPendingImage) {
      _touch(key, image, listenerTask);
    } else {
      image.dispose();
    }
    _pendingImages.remove(key);
    if (!listenedOnce) {
      pendingImage.removeListener();
    }
    if (!kReleaseMode && !listenedOnce) {
      listenerTask!.finish(arguments: <String, dynamic>{
        'syncCall': syncCall,
        'sizeInBytes': sizeBytes,
      });
      timelineTask!.finish(arguments: <String, dynamic>{
        'currentSizeBytes': currentSizeBytes,
        'currentSize': currentSize,
      });
    }
    listenedOnce = true;
  }
  final ImageStreamListener streamListener = ImageStreamListener(listener);
  pendingImage = _PendingImage(result, streamListener);
  if (trackPendingImage) {
    _pendingImages[key] = pendingImage;
  }
  // Listener is removed in [_PendingImage.removeListener].
  result.addListener(streamListener);
  return result;
}

这个是图片缓存的核心方法。

clearLiveImages

(调用此方法不会减轻内存压力,因为活动图像缓存仅跟踪同时由至少一个其他对象持有的图像实例。)

/// Clears any live references to images in this cache.
///
/// An image is considered live if its [ImageStreamCompleter] has never hit
/// zero listeners after adding at least one listener. The
/// [ImageStreamCompleter.addOnLastListenerRemovedCallback] is used to
/// determine when this has happened.
///
/// This is called after a hot reload to evict any stale references to image
/// data for assets that have changed. Calling this method does not relieve
/// memory pressure, since the live image caching only tracks image instances
/// that are also being held by at least one other object.
void clearLiveImages() {
  for (final _LiveImage image in _liveImages.values) {
    image.dispose();
  }
  _liveImages.clear();
}

清除此缓存中任何图像的现有引用。

如果一个图像的 [ImageStreamCompleter] 至少添加了一个侦听器并且从未达到零侦听器,则认为该图像是“现有的”。

[ImageStreamCompleter.addOnLastListenerRemovedCallback] 用于确定是否发生了这种情况。

在热重载之后调用此方法以清除对已更改资产的图像数据的任何过时引用。

调用此方法不会减轻内存压力,因为活动图像缓存仅跟踪同时由至少一个其他对象持有的图像实例。

答疑解惑

_pendingImages 正在加载中的缓存,这个有什么作用呢? 假设Widget1加载了图片A,Widget2也在这个时候加载了图片A,那这时候Widget就复用了这个加载中的缓存

_cache 已经加载成功的图片缓存

_liveImages 存活的图片缓存,看代码主要是在CacheImage之外再加一层缓存。收到内存警告时, 调用clear()方法清除缓存时, 并不是清除_liveImages, 因为官方解释: 因为这样做不会减少内存压力

参考链接

flutter图片组件源码解析

Flutter图片加载与缓存机制的深入探究

Flutter网络图片本地缓存的实现

以上就是Flutter加载图片流程之ImageCache源码解析的详细内容,更多关于Flutter图片加载ImageCache的资料请关注我们其它相关文章!

(0)

相关推荐

  • flutter图片组件核心类源码解析

    目录 导语 问题 Image的核心类图及其关系 网络图片的加载过程 网络图片数据的回调和展示过程 补上图片内存缓存的源码分析 如何支持图片的磁盘缓存 总结 导语 在使用flutter 自带图片组件的过程中,大家有没有考虑过flutter是如何加载一张网络图片的? 以及对自带的图片组件我们可以做些什么优化? 问题 flutter 网络图片是怎么请求的? 图片请求成功后是这么展示的? gif的每一帧是怎么支持展示的? 如何支持图片的磁盘缓存? 接下来,让我们带着问题一起探究flutter 图片组件的

  • 详解Flutter网络图片本地缓存的实现

    目录 一.问题 二.思路 三.实现 四.使用 五.缓存清理 一.问题 Flutter原有的图片缓存机制,是通过PaintingBinding.instance!.imageCache来管理缓存的,这个缓存缓存到的是内存中,每次重新打开APP或者缓存被清理都会再次进行网络请求,大图片加载慢不友好,且增加服务器负担. 二.思路 1.查看FadeInImage.assetNetwork.Image.network等几个网络请求的命名构造方法,初始化了ImageProvider. FadeInImage

  • Flutter 图片开发核心技能快速掌握教程

    目录 正文 使用网络图片 把网络图片缓存到磁盘 使用 assets 图片 适配浅色与深色模式 在不同的设备使用不同分辨率的图片 关于设备 dpr 不完全匹配的处理 忽略 dpr 信息 使用相册图片 使用相机拍摄的图片 使用内存图片 图片用做装饰 图片预加载 centerSlice centerSlice 只能放大,不能缩小. 全局缓存 ImageCache 的设置 图片类之间的关系 ImageProvider obtainKey(ImageConfiguration) 方法 resolve(Im

  • Flutter图片与文件选择器使用实例

    目录 引言 一.image_picker 1.安装 2.使用 3.属性 4.注意 二.flutter_document_picker 1.安装 2.使用 总结 引言 我已经一个多星期没碰过电脑了,今日上班,打开电脑的第一件事就是想着写点什么.反正大家都还沉浸在节后的喜悦中,还没进入工作状态,与其浪费时间,不如做些更有意义的事情. 今天就跟大家简单分享一下Flutter开发过程中经常会用到的图片和文件选择器. 一.image_picker 一个适用于iOS和Android的Flutter插件,能够

  • flutter中的资源和图片加载示例详解

    目录 封面图 指定相应的资源 资源绑定 Asset bundling 资源变体 加载资源 加载文本资源 加载图片 加载依赖包中的图片 最后 封面图 下个季度的目标是把前端监控相关的内容梳理出来,梳理出来之后可能会在公司内部做个分享- Flutter应用程序既括代码也包括一些其他的资产,我们通常这些资产为资源. 有时候我会思考assets这个单词,在程序中到底应该翻译为资产呢?还是翻译为资源?按照习惯,我们这里还是称为资源好了- 这些资源是一些与应用程序捆绑在一起和并且部署应用时会用到的的文件,在

  • Flutter加载图片流程之ImageProvider源码示例解析

    目录 加载网络图片 ImageProvider resolve obtainKey resolveStreamForKey loadBuffer load(被废弃) evict 总结 困惑解答 加载网络图片 Image.network()是Flutter提供的一种从网络上加载图片的方法,它可以从指定的URL加载图片,并在加载完成后将其显示在应用程序中.本节内容,我们从源码出发,探讨下图片的加载流程. ImageProvider ImageProvider是Flutter中一个抽象类,它定义了一种

  • Flutter加载图片流程MultiFrameImageStreamCompleter解析

    目录 MultiFrameImageStreamCompleter _handleCodecReady _decodeNextFrameAndSchedule _codec!.getNextFrame() _emitFrame(重要方法, 通知监听器触发回调,更新UI) _scheduleAppFrame _handleAppFrame addListener removeListener _maybeDispose 总结 MultiFrameImageStreamCompleter Multi

  • Flutter加载图片的多样玩法汇总

    目录 加载本地图片 圆角本地图片 效果图 代码 加载网络图片-本地图片占位图 加载网络图片-loading 效果 代码 圆角.边框.渐变 总结 加载本地图片 在项目目录下创建assets文件夹,再在其文件夹下创建images文件夹,后面将需要的图片复制到其中即可 在pubspec.yaml文件中添加引用 flutter: uses-material-design: true assets: - assets/images/ 在Container中加载本地图片 Container( width:

  • Flutter图片缓存管理ImageCache原理分析

    目录 引言 PaintingBinding 减少图片缓存 增大阀值 思考 引言 设计: 嗯? 这个图片点击跳转进详情再返回图片怎么变白闪一下呢?产品: 是啊是啊! 一定是个bug开发: 囧囧囧 在开发过程中, 也许你也遇到过这样一个场景. 进入一个页面后,前一个页面的图片都会闪白一下. 或者在列表中,加载很多列表项后,之前列表中的图片都需要重新加载.你有没有想过这一切的原因是什么呢? 没错! 它就是我们今天介绍的主人公 --- ImageCache 可能有些人对ImageCache还有些陌生,

  • Flutter加载图片流程之ImageCache源码示例解析

    目录 ImageCache _pendingImages._cache._liveImages maximumSize.currentSize clear evict _touch _checkCacheSize _trackLiveImage putIfAbsent clearLiveImages 答疑解惑 ImageCache const int _kDefaultSize = 1000; const int _kDefaultSizeBytes = 100 << 20; // 100 M

  • SpringBoot自定义加载yml实现方式,附源码解读

    目录 自定义加载yml,附源码解读 解决方法 源码解读 如何引入多个yml方法 方案一:无前缀,使用@Value注解 方案二:有前缀,无需@Value注解 自定义加载yml,附源码解读 昨天在对公司的微服务配置文件标准化的过程中,发现将原来的properties文件转为yml文件之后,微服务module中标记有@Configuration的配置类都不能正常工作了,究其原因,是由于@PropertySource属性默认只用于标记并告诉spring boot加载properties类型的文件 spr

  • MyBatis SqlSource源码示例解析

    目录 正文 SqlNode SqlNode接口定义 BoundSql SqlSource SqlSource解析时机 SqlSource调用时机 总结 正文 MyBatis版本:3.5.12. 本篇讲从mybatis的角度分析SqlSource.在xml中sql可能是带?的预处理语句,也可能是带$或者动态标签的动态语句,也可能是这两者的混合语句. SqlSource设计的目标就是封装xml的crud节点,使得mybatis运行过程中可以直接通过SqlSource获取xml节点中解析后的SQL.

  • Flink 侧流输出源码示例解析

    目录 Flink 侧流输出源码解析 源码解析 TimestampedCollector#collect CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput#collect ProcessOperator#ContextImpl#output CountingOutput#collect BroadcastingOutputCollector#collect RecordWriterOutput

  • JS前端操作 Cookie源码示例解析

    目录 引言 源码分析 使用 源码 分析 set get remove withAttributes & withConverter 总结 引言 前端操作Cookie的场景其实并不多见,Cookie也因为各种问题被逐渐淘汰,但是我们不用Cookie也可以学习一下它的思想,或者通过这次的源码来学习其他的一些知识. 今天带来的是:js-cookie 源码分析 使用 根据README,我们可以看到js-cookie的使用方式: // 设置 Cookies.set('name', 'value'); //

  • OpenMP task construct 实现原理及源码示例解析

    目录 前言 从编译器角度看 task construct Task Construct 源码分析 总结 前言 在本篇文章当中主要给大家介绍在 OpenMP 当中 task 的实现原理,以及他调用的相关的库函数的具体实现. 在本篇文章当中最重要的就是理解整个 OpenMP 的运行机制. 从编译器角度看 task construct 在本小节当中主要给大家分析一下编译器将 openmp 的 task construct 编译成什么样子,下面是一个 OpenMP 的 task 程序例子: #inclu

  • Python 装饰器常用的创建方式及源码示例解析

    目录 装饰器简介 基础通用装饰器 源码示例 执行结果 带参数装饰器 源码示例 源码结果 源码解析 多装饰器执行顺序 源码示例 执行结果 解析 类装饰器 源码示例 执行结果 解析 装饰器简介 装饰器(decorator)是一种高级Python语法.可以对一个函数.方法或者类进行加工.在Python中,我们有多种方法对函数和类进行加工,相对于其它方式,装饰器语法简单,代码可读性高.因此,装饰器在Python项目中有广泛的应用.修饰器经常被用于有切面需求的场景,较为经典的有插入日志.性能测试.事务处理

  • 如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据(附源码下载)

    ECharts地图主要用于地理区域数据的可视化,展示不同区域的数据分布信息.ECharts官网提供了中国地图.世界地图等地图数据下载,通过js引入或异步加载json文件的形式调用地图. 效果演示      源码下载 本文将结合实例讲解如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据,我们以中国地图为例,展示去年(2015年)我国各省份GDP数据.通过异步请求php,读取mysql中的数据,然后展示在地图上,因此本文除了你掌握前端知识外,还需要你了解PHP以及MySQL方

  • React Refs 的使用forwardRef 源码示例解析

    目录 三种使用方式 1. String Refs 2. 回调 Refs 3. createRef 两种使用目的 Refs 转发 createRef 源码 forwardRef 源码 三种使用方式 React 提供了 Refs,帮助我们访问 DOM 节点或在 render 方法中创建的 React 元素. React 提供了三种使用 Ref 的方式: 1. String Refs class App extends React.Component { constructor(props) { su

随机推荐