01.08.2027 202 материалов

Flutter Isolates: как обработка изображений перестала вешать интерфейс

Обработка изображений на главном потоке — классическая ошибка, из-за которой приложение тормозит и раздражает пользователя. Разбираемся, как Flutter Isolates решают эту проблему за несколько строк кода.

Flutter Isolates: как обработка изображений перестала вешать интерфейс

Если приложение виснет при сжатии фото, значит, вы тяжёлую работу взвалили на тот же поток, что рисует кнопки. Flutter Isolates — способ отправить эту работу в фон, не переписывая архитектуру.

Проблема: один поток на всё

Представьте типичное мобильное приложение: пользователь выбирает фото из галереи, приложение должно его сжать, уменьшить, может быть — наложить фильтр или распознать текст через OCR. Всё это процессороёмкие операции.

Начинающие разработчики часто делают их прямо в основном потоке — том самом, который отвечает за отрисовку интерфейса и обработку касаний. Результат предсказуем: интерфейс начинает подвисать, кнопки не реагируют, а Android в логах выбрасывает что-то вроде "Skipped 120 frames! The application may be doing too much work on its main thread".

Это не баг — это архитектурная ошибка. Один поток не может одновременно рисовать виджеты и пережимать шеститысячнопиксельную фотографию.

Что такое Isolate и почему это не поток

Dart, на котором написан Flutter, изначально однопоточный язык. Но он предоставляет механизм изолятов (Isolates) — отдельных сред выполнения с собственной памятью.

Ключевое отличие от классических потоков: изоляты не разделяют память. Они общаются между собой, пересылая сообщения. Это делает их безопаснее (нет гонок данных) и проще в отладке.

Схематично это выглядит так:

  • Главный изолят — рисует интерфейс, обрабатывает касания, обновляет состояние.
  • Фоновый изолят — выполняет тяжёлую работу: изменение размера, сжатие, фильтры, распознавание.

Пока фоновый изолят занят, главный продолжает рисовать кадры. Пользователь видит спиннер загрузки, а не зависший экран.

compute() — самый простой путь

Flutter предоставляет несколько механизмов для работы с изолятами. Самый простой — функция compute() из пакета flutter/foundation.dart. Она автоматически создаёт временный изолят, выполняет в нём переданную функцию и уничтожает его после завершения.

Выглядит это так:

import 'package:flutter/foundation.dart';
import 'package:image/image.dart' as img;
import 'dart:typed_data';

// Функция ДОЛЖНА быть верхнего уровня (top-level) или статической
Uint8List resizeImage(Uint8List bytes) {
  final image = img.decodeImage(bytes)!;
  final resized = img.copyResize(image, width: 1200);
  return Uint8List.fromList(img.encodeJpg(resized));
}

// Вызов — одна строка
final result = await compute(resizeImage, imageBytes);

Вот и всё. Вся тяжёлая работа ушла в фон. Главный изолят свободен для отрисовки.

Важное ограничение: функция, передаваемая в compute(), должна быть объявлена на верхнем уровне файла или быть статическим методом. Методы экземпляра не работают — изоляты не имеют доступа к состоянию виджета.

Что можно и нельзя передавать

Поскольку изоляты не разделяют память, данные между ними сериализуются. Это значит, что пересылать можно только простые типы:

  • Uint8List, String, int, double
  • List и Map (состоящие из простых типов)

Передача сложных объектов виджетов, открытых дескрипторов файлов или контроллеров не сработает. Об этом часто забывают — и потом гадают, почему данные не приходят.

Когда изоляты действительно нужны

Не каждая операция требует отдельного изолята. Создание изолята имеет небольшие накладные расходы, поэтому для мелких задач (пара миллисекунд) проще остаться в главном потоке.

Реальные сценарии, где изоляты оправданы:

  • Обработка изображений — ресайз, сжатие, наложение фильтров, конвертация форматов.
  • OCR и распознавание — текст, штрихкоды, QR-коды, лица.
  • Генерация документов — PDF, отчёты с форматированием.
  • Парсинг больших данных — JSON-файлы на сотни килобайт, CSV-экспорт.
  • Криптография — шифрование и дешифрование файлов.
  • Машинное обучение — инференс моделей на устройстве.

А вот сетевые запросы в изолят переносить смысла нет — HTTP-вызовы и так асинхронные и не блокируют UI.

Типичные ошибки

Обработка в build(). Метод build() может вызываться десятки раз при перестроении дерева виджетов. Если запустить сжатие изображения внутри него — приложение будет повторять тяжёлую работу снова и снова.

Отсутствие индикатора загрузки. Если обработка занимает 2–3 секунды, пользователь должен видеть спиннер или прогресс-бар. Без этого кажется, что приложение зависло.

Передача неподходящих объектов. Пытаетесь отправить в изолят экземпляр StatefulWidget? Не получится. Передавайте байты, строки и числа.

Изоляты для мелочей. Изменение переменной или простое вычисление не требуют отдельного изолята. Накладные расходы на создание превысят выгоду.

compute() vs Isolate.spawn() vs Isolate.run()

В оригинальной статье упоминаются два механизма: compute() и Isolate.spawn(). Но есть и третий — Isolate.run(), который в современных версиях Dart является предпочтительным. Как справедливо отметил в комментариях Google Developer Expert Randal Schwartz, compute() внутри себя уже вызывает Isolate.run().

Что выбрать:

  • compute() — простейший вариант для одноразовых задач. Бросил функцию, получил результат, забыл.
  • Isolate.run() — современный аналог, чуть более гибкий.
  • Isolate.spawn() — для долгоживущих фоновых задач с постоянным обменом сообщениями через SendPort и ReceivePort. Сложнее, но позволяет не создавать изолят заново каждый раз.

Для обработки изображений compute() более чем достаточен. Переходить к Isolate.spawn() стоит, если фоновый изолят должен работать постоянно — например, обрабатывать поток кадров с камеры.

Почему это важно для мобильных приложений

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

Промышленные Flutter-приложения используют изоляты повсеместно: камеры с фильтрами в реальном времени, соцсети, сжимающие фото перед загрузкой, сканеры документов с OCR, редакторы изображений. Если в вашем приложении есть хотя бы одна операция, которая занимает больше сотни миллисекунд — пора смотреть в сторону изолятов.

Итого

Flutter Isolates — это не продвинутая экзотика, а рабочий инструмент, который решает конкретную проблему: тяжёлые вычисления не должны мешать интерфейсу. compute() позволяет начать использовать его за пять минут, а эффект заметен сразу — плавные анимации, отзывчивые кнопки, довольные пользователи.

Если ваше приложение тормозит при обработке фотографий — проверьте, не делаете ли вы это на главном потоке. Скорее всего, ответ — да.