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

OSCHINA-MIRROR/CarGuo-GSYFlutterBook

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

Предположительно, разработчики Flutter сталкивались с проблемой выделения памяти при загрузке большого количества данных в списке изображений на платформе iOS. Это связано со специальным процессом загрузки изображений в Flutter.

На платформе Android основной объем памяти, используемый Flutter Image, не относится к памяти JVM, а связан с графическими данными. Такое использование памяти позволяет максимально эффективно использовать native память.

Основной процесс

По умолчанию Flutter загружает изображение через соответствующий ImageProvider. Затем данные передаются в PaintingBinding для кодирования, после чего возвращается объект ImageInfo, содержащий закодированное изображение и информацию для отрисовки.

Подробный процесс загрузки изображений можно найти здесь: «Десятая глава: Исследование процесса загрузки изображений».

Эта логика сама по себе не является проблемой, проблема заключается в том, что объект кэширования изображений в памяти Flutter — это объект ImageStream.

Кэш изображений в Flutter представляет собой асинхронный объект. Одним из недостатков кэширования асинхронных объектов является то, что до завершения декодирования изображения невозможно точно определить, сколько памяти будет использоваться, и большое количество одновременно загружаемых изображений может привести к значительному количеству операций ввода-вывода.Изначально самым простым решением было установление максимального размера и объема памяти через PaintingBinding.instance.maximumSize и PaintingBinding.instance.maximumSizeBytes. Однако такое решение не решало проблему переполнения памяти при загрузке длинного списка изображений, так как быстрое пролистывание списка могло вызывать одновременную загрузку большого количества изображений.

С версией 1.17 официальные разработчики представили более специфическое решение для этой проблемы: ScrollAwareImageProvider.

ScrollAwareImageProvider

В версии 1.17 можно заметить, что метод _resolveImage теперь использует ScrollAwareImageProvider, который принимает дополнительный параметр контекста DisposableBuildContext<State<Image>>>.

void _resolveImage() {
  final ScrollAwareImageProvider provider = ScrollAwareImageProvider<dynamic>(
    context: _scrollAwareContext,
    imageProvider: widget.image,
  );
  final ImageStream newStream =
    provider.resolve(createLocalImageConfiguration(
      context,
      size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
    ));
  assert(newStream != null);
  _updateSourceStream(newStream);
}

Зачем нужен ScrollAwareImageProvider? На самом деле объект ScrollAwareImageProvider используется в основном в методе resolveStreamForKey. Этот метод использует метод Scrollable.recommendDeferredLoadingForContext, чтобы определить, следует ли отложить загрузку текущего кадра, другими словами, это означает быстро ли происходит прокрутка.

Как же метод Scrollable.recommendDeferredLoadingForContext, являющийся статическим (static), определяет, быстро ли происходит прокрутка списка?Для этого требуется использовать метод getElementForInheritedWidgetOfExactType контекста (context), чтобы получить _ScrollableScope внутри Scrollable.

_ScrollableScope является виджетом типа InheritedWidget внутри Scrollable. Поскольку все скроллируемые представления в Flutter содержат Scrollable, если изображение находится в списке, можно получить _ScrollableScope с помощью context.getElementForInheritedWidgetOfExactType<_ScrollableScope>().

Получив _ScrollableScope, можно получить его внутренний ScrollPosition, а затем метод recommendDeferredLoading соответствующих ScrollPhysics, который позволяет определить, быстро ли происходит прокрутка списка. Таким образом, логика быстрой прокрутки реализуется в ScrollPhysics.

bool recommendDeferredLoading(double velocity, ScrollMetrics metrics, BuildContext context) {
  assert(velocity != null);
  assert(metrics != null);
  assert(context != null);
  if (parent == null) {
    final double maxPhysicalPixels = WidgetsBinding.instance.window.physicalSize.longestSide;
    return velocity.abs() > maxPhysicalPixels;
  }
  return parent.recommendDeferredLoading(velocity, metrics, context);
}

Для более подробного объяснения ScrollPhysics можно обратиться к статье «Волшебство ScrollPhysics и Simulation».Перейдем к методу resolveStreamForKey. Здесь видно, что когда Scrollable.recommendDeferredLoadingForContext возвращает true, процесс ожидания запускается. Это значит, что через SchedulerBinding вызывается метод resolveStreamForKey снова при следующем кадре, повторяя логику метода resolveStreamForKey. Если после этого прокрутка больше не считается быстрой, начинается обычный процесс загрузки изображения.

@override
void resolveStreamForKey(
  ImageConfiguration configuration,
  ImageStream stream,
  T key,
  ImageErrorListener handleError,
) {
  if (stream.completer != null || PaintingBinding.instance.imageCache.containsKey(key)) {
    imageProvider.resolveStreamForKey(configuration, stream, key, handleError);
    return;
  }
  if (context.context == null) {
    return;
  }
  if (Scrollable.recommendDeferredLoadingForContext(context.context)) {
    SchedulerBinding.instance.scheduleFrameCallback((_) {
      scheduleMicrotask(() => resolveStreamForKey(configuration, stream, key, handleError));
    });
    return;
  }
  imageProvider.resolveStreamForKey(configuration, stream, key, handleError);
}

Как показано выше в коде, можно заметить, что в методе resolveStreamForKey класса ScrollAwareImageProvider, когда stream.completer != null и существует кэш, происходит прямое использование существующего процесса загрузки. В случае быстрого скроллинга, если изображение еще не было загружено, загрузка откладывается.> В Flutter для предотвращения утечек памяти из-за хранения контекста context во время асинхронной загрузки изображений, был создан объект DisposableBuildContext.

Объект DisposableBuildContext хранит context через владение State. При вызове метода dispose поле _state устанавливается в null, тем самым освобождается владение State. Поэтому в вышеприведённом коде при условии context == null происходит немедленный выход из метода return.

Кроме того, метод resolveStreamForKey, который был упомянут ранее, является новым методом. Во время загрузки изображений с помощью ImageProvider используется метод ImageStream.resolve(), чтобы получить и вернуть объект ImageStream.

Метод resolveStreamForKey абстрагирует процесс работы imageCache и ImageStreamCompleter и переопределяет логику выполнения метода resolveStreamForKey в классе ScrollAwareImageProvider. Это позволяет при быстром скролле временно прекратить загрузку и декодирование изображений, тем самым снижая ненужное потребление памяти.

Хотя этот подход не гарантирует 100%-го решения проблемы OOM при загрузке изображений, он значительно оптимизирует использование памяти для изображений в списке. По данным, предоставленным официальными источниками, теоретически это может снизить потребление памяти до 70%.

Иллюстрация

Рекомендованная статья: Merged Defer image decoding when scrolling fast #49389## Рекомендованные ресурсы

Опубликовать ( 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