Как десятая статья в серии, данная работа подробно рассматривает процесс загрузки изображений в Flutter, анализирует интересные моменты этого процесса и завершает реализацию поддержки кэширования локальных изображений в Flutter.
В Flutter загрузка изображений осуществляется с помощью компонента Image
, который является StatefulWidget
. Из предыдущих статей мы знаем, что этот компонент имеет связанное с ним RenderObject
, отвечающее за размещение и рисование. Как именно происходит преобразование изображения в видимое изображение?
Процесс загрузки изображений в Flutter можно считать "не слишком сложным". Для наглядности можно посмотреть увеличенное изображение ниже. В качестве примера рассмотрим загрузку изображений через Интернет. Основные шаги следующие:- 1. Сначала Image
использует ImageProvider
для получения объекта ImageStream
_ImageState
использует ImageStream
для добавления прослушки и ожидания данных изображенияImageProvider
использует метод load
для загрузки и возврата объекта ImageStreamCompleter
ImageStream
связывается с ImageStreamCompleter
ImageStreamCompleter
скачивает изображение через HTTP, после чего с использованием PaintingBinding
производится кодировка, чтобы получить объект ui.Codec
, который может быть отрисован, и затем он упаковывается в объект ImageInfo
и возвращаетсяImageInfo
вызывает прослушивание ImageStream
, которое передается объекту RawImage
в состоянии _ImageState
RenderImage
в компоненте RawImage
рисует ui.Codec
из ImageInfo
с использованием метода paint
> Обратите внимание: здесь ui.Codec
и последующие ui.Image
и т.д., являются переименованными типами для разделения с другими типами при импорте: import 'dart:ui' as ui show Codec;
.Не кажется ли вам это запутанным? Отдохните! Мы постепенно разберёмся с этим процессом.
В процессе загрузки изображений в Flutter существуют три основных роли: - Image
: виджет для отображения изображений, который в конечном итоге рисуется с помощью внутреннего RenderImage
.
ImageProvider
: предоставляет способы загрузки изображений, такие как NetworkImage
, FileImage
, MemoryImage
, AssetImage
и т.д., чтобы получить ImageStream
, используемый для наблюдения за результатами.ImageStream
: объект для загрузки изображений, который в конечном итоге через ImageStreamCompleter
возвращает объект типа ImageInfo
. Внутри ImageInfo
содержится объект ui.Image
для рисования, созданный RenderImage
. Из большого схематического изображения выше следует, что сетевые изображения предоставляются для загрузки через провайдер NetworkImage
. Реализация различных провайдеров в целом схожа, основные методы, которые требуются для реализации, показаны ниже:Provider
,就像在 NetworkImage
案例中那样:他返回 SynchronousFuture<NetworkImage>(this)
,即自身对象 NetworkImage
。获取的键被用于 ImageProvider
作为内部缓存中的键值。Перевод:
Этот метод主要用于标识当前 Provider
,就像在 NetworkImage
案例中那样:他 返回 SynchronousFuture<NetworkImage>(this)
,即自身对象 NetworkImage
。获取的键被用于 ImageProvider
作为内部缓存中的键值。
Исправлено:
Этот метод主要用于标识当前 Provider
,就像在 NetworkImage
案例中那样:它 返回 SynchronousFuture<NetworkImage>(this)
,即自身的 NetworkImage
对象。获取的键被用于 ImageProvider
作为内部缓存中的键值。
В NetworkImage
сравнение производится на основе трёх параметров: runtimeType
, url
и scale
. Таким образом, помимо url
, значение scale
также влияет на объект кэширования.
Метод load
предназначен для загрузки данных, используя ключ, предоставленный методом obtainKey
.
Результатом выполнения этого метода является абстрактный объект ImageStreamCompleter
, который используется для управления и уведомления ImageStream
о получении dart:ui.Image
. В случае NetworkImage
используется его подкласс MultiFrameImageStreamCompleter
, способный обрабатывать анимацию с несколькими кадрами; если изображение состоит из одного кадра, загрузка завершается после первого кадра.
Ключевой момент ImageProvider
заключается в методе resolve
. Из схемы видно, что этот метод вызывается в жизненном цикле компонента Image
при вызовах методов didChangeDependencies
, didUpdateWidget
и reassemble
.
Как можно заметить из следующего источника кода, методы obtainKey
и load
используются внутри метода resolve
.
> Интересным объектом здесь является
Зона
!
В Flutter синхронные исключения могут быть пойманы с помощью конструкций
try-catch
, но асинхронные исключения, такие какFuture
, не могут быть пойманы текущей конструкциейtry-catch
.Поэтому в Dart существует концепция
Зоны
, которая позволяет указать исполняющему объекту зону исполнения, аналогично созданию песочницы. Внутри этой песочницы все исключения могут быть пойманы, перехватываться или изменять своё поведение кодом, например, все непредставленные исключения.Внутри методаresolve
используетсяPaintingBinding.instance.imageCache.putIfAbsent(key, () => load(key))
, гдеPaintingBinding
— это связывающий класс, использующий миксины для привязки кWidgetsFlutterBinding
. Мы уже говорили ранее, чтоWidgetsFlutterBinding
является исполнителем метода запускаrunApp
.Изображение закэшировано в виде единичного экземпляра внутри PaintingBinding.instance.imageCache.
Как показано на следующей схеме, внутренний метод putIfAbsent
проверяет наличие кеша в памяти по ключу, чтобы определить, существует ли уже кеш данного объекта или он находится в процессе кеширования. Если это так, то возвращается ImageStreamCompleter
, а если нет, то вызывается loader
для загрузки и возврата.
Важно отметить, что в данный момент кеш имеет два состояния. Возвращаемый ImageStreamCompleter
не обязательно указывает на завершение загрузки изображения; поэтому при первой загрузке используется _PendingImage
для обозначения того, что изображение с данным ключом находится в состоянии загрузки, и добавляется слушатель для замены его кешем _CacheImage
после завершения загрузки.
Обратите внимание, здесь концепция кеширования немного отличается от нашей привычной. Обычно мы кэшируем пары ключ-битмап, представляющие фактические данные для рисования. Однако в Flutter кэшируются только объекты типа ImageStreamCompleter
, а не сами объекты dart:ui.Image
для рисования.### 3. ImageStreamCompleter
ImageStreamCompleter является абстрактным объектом, который主要用于管理并通知 ImageStream,以及处理包含 ImageInfo 对象的数据。该对象表示 dart:ui.Image。
接下来我们来探讨在 NetworkImage 中实现的 MultiFrameImageStreamCompleter 类。如下面的代码所示,MultiFrameImageStreamCompleter 使用 codec 参数获取渲染数据,而这些数据通过 _loadAsync 方法加载。此方法通过 HTTP 加载图像,并使用 PaintingBinding 中的 ImageCodec 处理图像数据,将其转换为可以由绘制引擎使用的数据格式。
在 MultiFrameImageStreamCompleter 内部,ui.Codec 被包装成 ui.Image 通过 ImageInfo 并依次传递回 _ImageState,在那里 setState 将数据传递给 RenderImage 进行绘制。
就是这样吗?现在当你回到最初的架构时,一切应该变得更加清晰了?
NetworkImage
, мы знаем, что изображение загружается через HTTP в методе _loadAsync
. Самый простой способ — скопировать код NetworkImage
, а затем модифицировать _loadAsync
, чтобы он поддерживал чтение локального кэша перед HTTP-загрузкой и сохранение данных после загрузки.Используя плагин flutter_cache_manager
, как показано ниже, можно быстро и просто реализовать локальное кэширование изображений:Future<ui.Codec> _loadAsync(NetworkImage key) async {
assert(key == this);
// Добавляем это начало
// flutter_cache_manager DefaultCacheManager
final fileInfo = await DefaultCacheManager().getFileFromCache(key.url);
if (fileInfo != null && fileInfo.file != null) {
final Uint8List cacheBytes = await fileInfo.file.readAsBytes();
if (cacheBytes != null) {
return PaintingBinding.instance.instantiateImageCodec(cacheBytes);
}
}
// Добавляем это конец
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 запрос завершился ошибкой, statusCode: ${response?.statusCode}, $resolved');
final Uint8List bytes = await consolidateHttpClientResponseBytes(response);
if (bytes.lengthInBytes == 0)
throw Exception('NetworkImage является пустым файлом: $resolved');
// Добавляем это начало
await DefaultCacheManager().putFile(key.url, bytes);
// Добавляем это конец
return PaintingBinding.instance.instantiateImageCodec(bytes);
}
ImageCache
的问题:它缓存的是一个异步对象。这意味着在图像解码完成之前,我们无法确定实际会占用多少内存。此外,大量图像的加载可能会导致大量的I/O操作。如果需要进一步的具体细节或者有特定的要求,请告知以便进行适当的调整和补充。А в Flutter размер кэша ImageCache
по умолчанию составляет
const int _kDefaultSize = 1000;
const int _kDefaultSizeBytes = 100 << 20; // 100 MB
Поэтому простым решением будет установка максимального размера кэша следующим образом: PaintingBinding.instance.imageCache.maximumSize = 100;
. Также можно приостановить загрузку изображений, когда страница становится невидимой.#### 2.9-Изображение
В компоненте Image
эффект .9-изображения можно настроить с помощью параметра centerSlice
.
Таким образом, десятая статья наконец завершена! (///▽///)
Вы можете оставить комментарий после Вход в систему
Неприемлемый контент может быть отображен здесь и не будет показан на странице. Вы можете проверить и изменить его с помощью соответствующей функции редактирования.
Если вы подтверждаете, что содержание не содержит непристойной лексики/перенаправления на рекламу/насилия/вульгарной порнографии/нарушений/пиратства/ложного/незначительного или незаконного контента, связанного с национальными законами и предписаниями, вы можете нажать «Отправить» для подачи апелляции, и мы обработаем ее как можно скорее.
Опубликовать ( 0 )