> Какие методы жизненного цикла приложения во Flutter используются для отслеживания сворачивания и разворачивания (Flutter, Android)

Уровень: senior · Роль: mobile · Категория: Технические вопросы

Компании: PashaPay

Стек: Flutter, Android

> Пример ответа

Короткий ответ

Во Flutter отслеживание сворачивания и разворачивания приложения выполняется через WidgetsBindingObserver и метод didChangeAppLifecycleState(). Он возвращает значения AppLifecycleState.inactive, paused, resumed, detached и hidden. Для Android-специфичных сценариев также можно использовать AppLifecycleListener (новый API) или нативные колбэки через MethodChannel.

Подробное объяснение

Основной механизм - подписка на изменения жизненного цикла через WidgetsBinding.instance.addObserver(this) в State-классе. Метод didChangeAppLifecycleState вызывается при каждом переходе состояния.

Состояния AppLifecycleState:

  • resumed - приложение активно и видимо, пользователь взаимодействует
  • inactive - приложение видимо, но не получает события (например, при звонке, свайпе шторки)
  • paused - приложение свернуто, не видимо (Android onPause/onStop)
  • detached - приложение ещё прикреплено к движку, но будет уничтожено (Android onDetachedFromWindow)
  • hidden - появилось в Flutter 3.13, означает что приложение скрыто, но может быть в состоянии inactive или paused

Важно: на Android переход в фоновый режим обычно даёт последовательность inactivehiddenpaused. При возврате - resumed.

Также существует AppLifecycleListener (Flutter 3.13+), который предоставляет более удобный API с отдельными колбэками: onResume, onPause, onInactive, onHide, onShow, onDetach, onRestart (для Android).

На практике

Типичный сценарий - сохранение состояния, остановка анимаций, закрытие WebSocket-соединений при сворачивании, и восстановление при разворачивании.

Для Android-специфичных кейсов (например, отслеживание onUserLeaveHint или onTrimMemory) потребуется MethodChannel или использование ActivityAware из плагинов.

Важно помнить: didChangeAppLifecycleState не гарантирует вызов при каждом переходе на Android - например, при повороте экрана может быть только inactive без paused.

Пример кода

class AppLifecycleObserver extends StatefulWidget {
  const AppLifecycleObserver({super.key, required this.child});
  final Widget child;

  @override
  State<AppLifecycleObserver> createState() => _AppLifecycleObserverState();
}

class _AppLifecycleObserverState extends State<AppLifecycleObserver>
    with WidgetsBindingObserver {
  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    switch (state) {
      case AppLifecycleState.resumed:
        // восстановить соединения, перезапустить анимации
        break;
      case AppLifecycleState.inactive:
        // приостановить тяжёлые операции
        break;
      case AppLifecycleState.paused:
        // сохранить состояние, закрыть WebSocket
        break;
      case AppLifecycleState.hidden:
        // скрыть чувствительные данные
        break;
      case AppLifecycleState.detached:
        // финальная очистка
        break;
    }
  }

  @override
  Widget build(BuildContext context) => widget.child;
}

Альтернатива через AppLifecycleListener:

late final AppLifecycleListener _listener;

@override
void initState() {
  super.initState();
  _listener = AppLifecycleListener(
    onResume: () => _handleResume(),
    onPause: () => _handlePause(),
    onHide: () => _handleHide(),
    onShow: () => _handleShow(),
    onInactive: () => _handleInactive(),
    onDetach: () => _handleDetach(),
    onRestart: () => _handleRestart(), // Android only
  );
}

@override
void dispose() {
  _listener.dispose();
  super.dispose();
}

Как отвечать на собеседовании

Начните с WidgetsBindingObserver и didChangeAppLifecycleState. Перечислите все состояния и их значение на Android. Упомяните, что на Android порядок состояний может отличаться от iOS. Добавьте про AppLifecycleListener как более современный API. Приведите пример практического использования - сохранение черновика или остановка стриминга. Если спросят про глубину - упомяните MethodChannel для нативных колбэков.

Что проверяет интервьюер

  • Понимание разницы между inactive, paused и hidden
  • Знание порядка вызовов на Android и iOS
  • Умение корректно подписываться и отписываться от observer
  • Понимание, что didChangeAppLifecycleState не покрывает все Android-сценарии (например, onTrimMemory)
  • Знание современного API AppLifecycleListener

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

  • Забывают удалить observer в dispose - утечка памяти
  • Путают inactive и paused, считая их эквивалентами
  • Полагают, что paused всегда вызывается при сворачивании - на Android бывает только inactive + hidden
  • Пытаются выполнять тяжёлые операции в didChangeAppLifecycleState - это блокирует UI
  • Не учитывают, что при повороте экрана на Android вызывается inactive, но не paused
  • Используют WidgetsBindingObserver в StatelessWidget без State - observer требует жизненный цикл State

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью