Предположительно, разработчики Flutter сталкивались с проблемой выделения памяти при загрузке большого количества данных в списке изображений на платформе iOS. Это связано со специальным процессом загрузки изображений в Flutter.
На платформе Android основной объем памяти, используемый Flutter Image
, не относится к памяти JVM, а связан с графическими данными. Такое использование памяти позволяет максимально эффективно использовать native память.
По умолчанию Flutter загружает изображение через соответствующий ImageProvider
. Затем данные передаются в PaintingBinding
для кодирования, после чего возвращается объект ImageInfo
, содержащий закодированное изображение и информацию для отрисовки.
Подробный процесс загрузки изображений можно найти здесь: «Десятая глава: Исследование процесса загрузки изображений».
Эта логика сама по себе не является проблемой, проблема заключается в том, что объект кэширования изображений в памяти Flutter — это объект ImageStream
.
Кэш изображений в Flutter представляет собой асинхронный объект. Одним из недостатков кэширования асинхронных объектов является то, что до завершения декодирования изображения невозможно точно определить, сколько памяти будет использоваться, и большое количество одновременно загружаемых изображений может привести к значительному количеству операций ввода-вывода.Изначально самым простым решением было установление максимального размера и объема памяти через PaintingBinding.instance.maximumSize
и PaintingBinding.instance.maximumSizeBytes
. Однако такое решение не решало проблему переполнения памяти при загрузке длинного списка изображений, так как быстрое пролистывание списка могло вызывать одновременную загрузку большого количества изображений.
С версией 1.17 официальные разработчики представили более специфическое решение для этой проблемы: 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 )