1 В избранное 0 Ответвления 0

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

Присоединиться к Gitlife
Откройте для себя и примите участие в публичных проектах с открытым исходным кодом с участием более 10 миллионов разработчиков. Приватные репозитории также полностью бесплатны :)
Присоединиться бесплатно
Клонировать/Скачать
Flutter-10.md 17 КБ
Копировать Редактировать Web IDE Исходные данные Просмотреть построчно История
Отправлено 10.03.2025 00:06 5767d61

Как десятая статья в серии, данная работа подробно рассматривает процесс загрузки изображений в Flutter, анализирует интересные моменты этого процесса и завершает реализацию поддержки кэширования локальных изображений в Flutter.

Ссылка на полную серию статей:

Полная серия статей по Flutter
Серия статей "Мир Flutter"

В Flutter загрузка изображений осуществляется с помощью компонента Image, который является StatefulWidget. Из предыдущих статей мы знаем, что этот компонент имеет связанное с ним RenderObject, отвечающее за размещение и рисование. Как именно происходит преобразование изображения в видимое изображение?

1. Процесс загрузки изображений

Процесс загрузки изображений в Flutter можно считать "не слишком сложным". Для наглядности можно посмотреть увеличенное изображение ниже. В качестве примера рассмотрим загрузку изображений через Интернет. Основные шаги следующие:- 1. Сначала Image использует ImageProvider для получения объекта ImageStream

    1. Затем _ImageState использует ImageStream для добавления прослушки и ожидания данных изображения
    1. Далее ImageProvider использует метод load для загрузки и возврата объекта ImageStreamCompleter
    1. После того как ImageStream связывается с ImageStreamCompleter
    1. Затем ImageStreamCompleter скачивает изображение через HTTP, после чего с использованием PaintingBinding производится кодировка, чтобы получить объект ui.Codec, который может быть отрисован, и затем он упаковывается в объект ImageInfo и возвращается
    1. Затем ImageInfo вызывает прослушивание ImageStream, которое передается объекту RawImage в состоянии _ImageState
    1. Наконец, 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. Реализация различных провайдеров в целом схожа, основные методы, которые требуются для реализации, показаны ниже:

1. obtainKeyЭтот метод主要用于标识当前 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 также влияет на объект кэширования.

2. load(T key)

Метод load предназначен для загрузки данных, используя ключ, предоставленный методом obtainKey.

Результатом выполнения этого метода является абстрактный объект ImageStreamCompleter, который используется для управления и уведомления ImageStream о получении dart:ui.Image. В случае NetworkImage используется его подкласс MultiFrameImageStreamCompleter, способный обрабатывать анимацию с несколькими кадрами; если изображение состоит из одного кадра, загрузка завершается после первого кадра.

3. resolve

Ключевой момент 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 进行绘制。

就是这样吗?现在当你回到最初的架构时,一切应该变得更加清晰了?

2. Кэширование локальных изображенийПонимая вышеописанный процесс, мы знаем, что Flutter реализует кэширование изображений в оперативной памяти, но не реализует локальное кэширование изображений. Поэтому наш подход должен начинаться с ImageProvider. После анализа 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);
}

III. Другие дополнения

1. Количество кэшированных объектовВ статье анализа памяти от 閒魚关于Flutter线上应用的[内存储分析文章]中的内容已经被转换为翻译部分,请确认是否需要保留原文链接或进一步的信息补充。根据提供的信息,这里主要提到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 )

Вы можете оставить комментарий после Вход в систему

1
https://api.gitlife.ru/oschina-mirror/CarGuo-GSYFlutterBook.git
git@api.gitlife.ru:oschina-mirror/CarGuo-GSYFlutterBook.git
oschina-mirror
CarGuo-GSYFlutterBook
CarGuo-GSYFlutterBook
master