Flutter: Состояние экземпляра не сохраняется, когда приложение убивает ОС

Созданный на 12 нояб. 2016  ·  139Комментарии  ·  Источник: flutter/flutter

Что такое состояние экземпляра и почему оно существует

На Android активность может быть убита в любое время с помощью системы. Обычно это происходит, когда Android требуется память, когда ваша Activity не находится на переднем плане, или из-за необработанного изменения конфигурации, такого как изменение языкового стандарта.
Чтобы пользователю не приходилось перезапускать то, что он делал с нуля, когда Android убил Activity, система вызывает onSaveInstanceState(…) когда Activity приостановлена, и приложение должно сохранять свои данные в Bundle , и передает сохраненный пакет как в onCreate(…) и в onRestoreInstanceState(…) когда задача возобновляется, если действие было прекращено системой.

Вопрос об этом в трепете

В приложениях флаттера, которые я пробовал (Галерея Flutter и базовый проект со счетчиком нажатий FAB), если я открою достаточно приложений, чтобы Android убил активность приложения flutter, все состояние будет потеряно, когда я вернусь к активности флаттера (в то время как не удалив задачу из последних).

Действия по воспроизведению

  1. Установить галерею Flutter
  2. Откройте настройки устройства и в настройках разработчика включите «Не сохранять действия». (см. скриншот)
    dev_option
    Это позволит имитировать, когда Android убивает Activity, потому что ему не хватает памяти, и вы не находитесь на переднем плане.
  3. Откройте приложение Flutter Gallery и перейдите в любое место, кроме главного экрана.
  4. Перейдите в программу запуска, нажав кнопку «Домой» на устройстве.
  5. Нажмите кнопку обзора и вернитесь в галерею Flutter. Вот ошибка.

Что ожидается: приложение находится в том же состоянии, в котором мы остановились, с нетронутым пользовательским интерфейсом.
Что происходит: действие перезапускается с нуля, теряется все состояние пользовательского интерфейса, даже очень длинные формы.

P2 annoyance crowd routes framework new feature

Самый полезный комментарий

Быстрое обновление: в настоящее время я активно работаю над поиском решений этой проблемы.

Все 139 Комментарий

https://github.com/flutter/flutter/issues/3427 также, вероятно, связано.

@eseidelGoogle Верно. В каком формате можно сохранить состояние активности флаттера, если оно вообще возможно в текущей версии?

Сейчас мы ничего не делаем, чтобы ничего спасти.

Сама структура имеет очень мало состояния, которое стоит сохранять - это вся анимация и тому подобное - так что может быть, мы всегда оставляем это на усмотрение приложения. Тем не менее, нам, вероятно, следует раскрыть его на уровне дротиков. (Сейчас он доступен только на уровне Java.)

@Hixie На Android состояние всех представлений фреймворка автоматически сохраняется и восстанавливается системой, для чего от разработчика требуется только вручную сохранить часть состояния экземпляра, не относящуюся к пользовательскому интерфейсу. Не должно работать одинаково для всех виджетов пользовательского интерфейса, чтобы разработчикам не приходилось каждый раз писать весь шаблон, чтобы сохранять, где находился пользователь, положение прокрутки, включенное состояние какой-либо кнопки, состояние предыдущего экрана и т. Д. на…?

Во Flutter очень мало состояний фреймворка, которые нужно сохранять. Например, включенное состояние кнопки - это не состояние, это ввод, предоставляемый приложением. Текущая история маршрута хранится в платформе, но не сохраняется в состоянии, которое структура может перестроить (поскольку это все экземпляры объектов, предоставленные кодом приложения). Положение прокрутки - это единственное, что мы действительно можем сохранить (и в настоящее время мы сохраняем это в магазине, но не в том, что сохраняется в приложении).

Но в любом случае у нас обязательно должно получиться лучше, чем сегодня.

Как опытный разработчик Android, я хотел бы принять участие в разговоре с разработчиками iOS, когда это будет обсуждаться, поэтому flutter может стать идеальной платформой для создания приложений для iOS и Android.
Я думаю, что способность фреймворка к сохранению может быть важна для сохранения данных, предпочтений, данных кеша и состояний наиболее удобным для разработчика способом.

Ой, я не понимал, что это было так? Это на самом деле критично, и стоит упомянуть, что на устройствах с меньшим объемом памяти процесс может быть довольно легко прерван!

Как насчет сохранения PageStore (или связанного с ним) в виде байтового потока через serialization.dart в onSaveInstanceState ?

@Takhion Не могли бы вы связать

@LouisCAD уверен, что он здесь: https://github.com/flutter/flutter/blob/master/packages/flutter/lib/src/widgets/page_storage.dart

Я думаю, вам нужно сэкономить больше: по крайней мере, текущий маршрут (ы) и, возможно, какое-то состояние?
Как ты думаешь, @Hixie?

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

Предоставляем ли мы необходимые события жизненного цикла («вас вот-вот убьют!», «Поздравляю, теперь вы восстановлены») в код Dart, чтобы разработчики могли обрабатывать сохраняющееся состояние? Кажется, этого достаточно, чтобы дать разработчикам возможность исследовать решения. Мысли?

@sethladd ИМХО недостаточно просто

@LouisCAD благодарит за отзыв. Я думаю по шагам ... какой шаг первый? У нас уже есть события жизненного цикла?

@sethladd все, что я смог найти, это то , что не предоставляет никакого обратного вызова для сохранения / восстановления состояния экземпляра

@Hixie, вы упомянули в # 3427, что у нас есть крючки для жизненного цикла. Вы имели в виду

Использует ли Flutter по умолчанию несколько действий? (например, если вы перейдете на новый экран).

Смерть процесса довольно распространена, пользователь нажимает кнопку «Домой» и запускает какое-то другое приложение, интенсивно использующее память / ЦП, и ваше приложение будет убито в фоновом режиме (или ваше приложение слишком долго находилось в фоновом режиме). Это неотъемлемая часть ОС Android - каждый экран (Activity) должен иметь возможность сохранять и восстанавливать свое состояние, а процесс может быть остановлен в любое время после onStop ().

Android воссоздает backstack Activity, если пользователь возвращается к убитому приложению, сначала создается верхнее Activity, а затем действия в backstack воссоздаются по запросу, если вы вернетесь в историю backstack. Это может быть более серьезной проблемой, если Flutter использует несколько Activity (не уверен, что это так).

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

Я написал статью о смерти процесса на Android https://medium.com/inloop/android-process-kill-and-the-big-implications-for-your-app-1ecbed4921cb

Также не существует (чистого) способа предотвратить удаление вашего приложения Android после того, как оно onStop () (после нажатия кнопки «Домой» или если приложение просто не является текущим приложением переднего плана). Так что как-то вы должны с этим справиться. Виджеты Android по умолчанию автоматически сохраняют свое состояние (например, введенный текст в EditText) в экземпляр Bundle. Ваша Activity уведомит вас о необходимости сохранить ваше состояние, вызвав onSaveInstanceState (Bundle) . Так что, возможно, Flutter сможет перенаправить этот обратный вызов обратном вызове жизненного цикла onCreate (Bundle savedInstanceState) или onRestoreInstanceState (Bundle savedInstanceState) в своей деятельности.

Итак, напомним, Flutter может пересылать обратные вызовы onSaveInstanceState () и onRestoreInstanceState () разработчику. В идеале вы также должны обернуть объект Android Bundle во что-то, что также можно использовать во Flutter. Следующим шагом будет то, что все виджеты Flutter внутри экрана также будут уведомлены об этих обратных вызовах и будут использовать их для сохранения своего текущего состояния.
Затем ОС Android возьмет пакет и фактически сохранит его на диске, чтобы он не был потерян в случае остановки процесса и его можно снова десериализовать.

Удачи с этим :-). Пожалуйста, будьте осторожны и не привносите в Flutter слишком много всего этого адского состояния / жизненного цикла Android.

Я не уверен, как это работает на iOS, но я думаю, что есть что-то похожее, но использовать его не «обязательно» (?).

Для меня подойдет обратный вызов жизненного цикла. Я бы просто сохранил / загрузил сериализованное состояние редукции.

Спасибо всем за отзывы! @Takhion тоже предложил здесь свою помощь. Может быть, дизайн API и библиотека - хорошее начало? Если эта библиотека будет работать, мы можем рассмотреть возможность более формальной интеграции. Кроме того, библиотека поможет определить, что нам нужно делать на низкоуровневом движке (если есть). В основном: каково конкретное предложение API?

Использует ли Flutter по умолчанию несколько действий? (например, если вы перейдете на новый экран)

@DanielNovak Flutter использует свою собственную систему «маршрутизации» и по умолчанию интегрируется с Android через одно представление в одном действии. У вас может быть гибридное приложение Android / Flutter и, как таковое, потенциально несколько действий и представлений Flutter, но в этом случае вы можете легко сохранить / восстановить состояние экземпляра напрямую через Android.

Я бы просто сохранил / загрузил сериализованное состояние редукции

@zoechi, вы, вероятно, не захотите этого делать, потому что Bundle с сохраненным состоянием экземпляра должен пройти через IPC с жестким ограничением в 1 МБ для всего состояния вашего приложения Android. Состояние экземпляра, по определению, должно быть только теми частями данных, которые позволили бы вам воссоздать те же условия любой текущей активности, выполняемой пользователем, а именно: ввод текста, положение прокрутки, текущая страница и т. Д. Все остальное должно либо сохраняться на диске или выводиться из другого состояния.

Flutter предоставляет событие жизненного цикла Android onPause . Событие жизненного цикла Android onDestroy () не
Для совместимости с onSaveInstanceState () и onRestoreInstanceState () Android, возможно, имеет смысл добавить аналогичные методы в виджеты Flutter.
@sethladd @LouisCAD Кто-нибудь создал проектное предложение?

@ raju-bitter onPause () не оптимален, лучше подключиться к onSaveInstanceState (). Вот javadoc от onSaveInstanceState, в котором упоминается, что onPause () вызывается более регулярно, чем onSaveInstanceState (вы бы запускали сохранение состояния чаще, чем необходимо, или когда это не нужно вообще):

Не путайте этот метод с обратными вызовами жизненного цикла активности, такими как onPause (), который всегда вызывается, когда действие помещается в фоновый режим или на пути к уничтожению, или onStop (), который вызывается перед уничтожением. Один из примеров того, когда вызываются onPause () и onStop (), а не этот метод, - это когда пользователь переходит обратно от действия B к действию A: нет необходимости вызывать onSaveInstanceState (Bundle) на B, потому что этот конкретный экземпляр никогда не будет восстановлен. , поэтому система избегает его вызова. Пример, когда вызывается onPause (), а не onSaveInstanceState (Bundle), - это когда действие B запускается перед действием A: система может избежать вызова onSaveInstanceState (Bundle) для действия A, если оно не было убито в течение времени жизни B, поскольку состояние пользовательского интерфейса A останется неизменным.

@Takhion

Пакет состояния экземпляра должен пройти IPC с жестким ограничением в 1 МБ для всего состояния вашего приложения Android.

Это не моя проблема.
Мне просто нужно знать, когда сохранять / восстанавливать, сохранение себя на диске для меня нормально.

Распространение onSaveInstanceState / onRestoreInstanceState - простая часть: Flutter содержится в представлении и имеет надлежащий доступ к этим обратным вызовам. Я пытаюсь создать проектное предложение и прототип для фактической автоматической сериализации объектов, содержащихся в определенном «репозитории», чтобы его использование стало безболезненным (в отличие от необходимости делать все вручную).

@Takhion, ты ветер под моими крыльями. Не могу дождаться предложения!

Хорошо, поэтому я создаю PR для этого и попадаю в затруднительное положение: [ View.onSaveInstanceState ] (или [ Activity.onSaveInstanceState ], кстати) требуют синхронного предоставления сохраненного состояния экземпляра и из мой опыт (и этот [комментарий]) похоже, что сообщения хоста / платформы могут быть только асинхронными . Я подозреваю, что это потому, что они используют очередь сообщений Android.

@sethladd @Hixie @eseidelGoogle @ jason-simmons, пожалуйста, скажите мне, что есть простой способ сделать один синхронный / блокирующий вызов с Android на Flutter, который не требует специального кода C / C ++, поскольку [ dart:jni вышел на пенсию]?

Копия @ mravn-google

Асинхронный характер сообщений платформы проистекает из того факта, что пользовательский интерфейс Dart выполняется в другом потоке, чем тот, который используется для обратных вызовов платформы Android. Но вы всегда можете превратить асинхронный вызов API в синхронный с помощью защелки:

final MethodChannel channel = new MethodChannel(messenger, someName);
final CountDownLatch latch = new CountDownLatch(1);
channel.invokeMethod("someMethod", someArguments, new MethodChannel.Result() {
  <strong i="6">@Override</strong>
  public void success(Object result) {
    // handle successful invocation
    latch.countDown();
  }
  <strong i="7">@Override</strong>
  public void error(String errorCode, String errorMessage, Object errorDetails) {
    // handle failed invocation
    latch.countDown();
  }
  <strong i="8">@Override</strong>
  public void notImplemented() {
    // handle invocation of unimplemented method
    latch.countDown();
  }
});
try {
  latch.await();
} catch (InterruptedException e) {
  // handle interruption
}

Вы можете объявить переменную final AtomicReference<SomeType> (или аналогичную) рядом с защелкой, если вам нужно передать значения из местоположений // handle xxx в код, который выполняется после возврата latch.await() .

спасибо @ mravn-google!

@ mravn-google, это как раз первый подход, который я пробовал, но основной поток на Android просто зависает на вызове await() навсегда :(

@Takhion Верно, конечно.

вы всегда можете превратить асинхронный вызов API в синхронный с помощью защелки

... если асинхронный ответ не должен быть доставлен в поток, в котором вы уже находитесь: - /

В таком случае нам, кажется, нужно какое-то синхронизирующее рукопожатие для onSaveInstanceState . Знаем ли мы о других подобных обратных вызовах, для которых требуется синхронный ответ, связанный с вычислениями на стороне Dart? Для этого потребуется синхронный обмен сообщениями на платформе общего назначения. В противном случае мы могли бы сделать что-нибудь специально для onSaveInstanceState .

@ mravn-google, к сожалению, Android заполнен этими неприятными "синхронными окнами возможностей", где все должно произойти, прежде чем вернуться из вызова метода! В голове: много событий жизненного цикла Activity , методов в Broadcast Receiver , SyncAdapter и т. Д. Я искренне удивлен, что это не было большой проблемой раньше!

На мой взгляд, если мы хотим, чтобы Flutter действительно достиг паритета функций с тем, что возможно через «родные» API, то наличие синхронной передачи сообщений просто необходимо.

@ mit-mit Я имел в виду SystemChannels.lifecycle и все, что его использует, да.

Это то же самое, что и https://github.com/flutter/flutter/issues/7560 ?

@sethladd Думаю, да.

@ mravn-google, спасибо! Я закрыл # 7560 в пользу этого, просто чтобы избавиться от дублирования и централизовать разговор.

Я склоняюсь к добавлению синхронного аналога к MethodChannel для такого рода связи между платформой и Dart.

@ mravn-google, как дела? Могу ли я помочь?

@Takhion Спасибо! Скоро должно быть. Я работаю над каналом синхронного метода и надеюсь получить PR в течение дня или двух. Как только он появится, я попрошу вашего обзора. Я хочу как можно скорее подключить новый канал до onSaveInstanceState чтобы проверить, что все работает так, как задумано в этом сценарии. Если у вас уже есть пример приложения, в которое вы могли бы внести свой вклад, это было бы здорово.

@ mravn-google круто! У меня нет приложения, потому что я напрямую модифицирую фреймворк, но я определенно могу предоставить образец. Кстати, я использую BinaryMessages напрямую, так как на стороне Android нет необходимости в кодеке (это просто байты), это нормально?

вы, ребята, молодцы.

@Takhion Прямое использование BinaryMessages вполне нормально, если вам не нужен кодек. Я добавлю

void setSynchronousMessageHandler(String channel, ByteData handler(ByteData message))

в этот класс.

@Takhion Ударьте это. Мы не можем просто выполнять синхронные вызовы виртуальной машины Dart для вызова handler . Таким образом, BinaryMessages останется без изменений. Вместо этого, как обычно, все будет асинхронно с точки зрения кода Dart. Изменения будут внесены в BinaryMessenger и его реализации на стороне платформы. Я добавлю

ByteBuffer sendSynchronous(String channel, ByteBuffer message);

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

@ mravn-google работает для меня, так как это не мешает нам отправлять обратно данные со стороны Dart, даже если это асинхронный вызов: +1:
Думаю, не будет никаких оптимизаций, если использовать Future.value(...) в асинхронном ответе Dart?

@Takhion Пожалуйста, взгляните на https://github.com/flutter/engine/pull/4358

Я вижу, вы в основном говорите о платформе Android. Но, на самом деле, iOS также имеет несколько похожий механизм для сохранения / восстановления состояния пользовательского интерфейса, когда приложение по какой-то причине было убито. Например, когда пользователь переключается на другое приложение, текущее приложение приостанавливается и может быть даже убито из-за ограничений памяти. Вы можете найти больше информации об этом процессе в официальных документах .

Разработчики iOS ожидают, что эта процедура сохранения / восстановления будет почти автоматической. Думаю, если ваша цель - создать действительно кроссплатформенный фреймворк, вы обязательно должны это учитывать.

Можно подумать, что что-то, что нацелено на Android как платформу, по крайней мере, пытается подчиняться основам контракта Activity ...

@Zhuinden, давайте не будем жаловаться на вещи, которые все еще находятся в

Другие синхронные API: onLowMemory и onTrimMemory и onConfigurationChanged не знают, есть ли в IOS аналогичные API или механизмы.

Возможно, мы не захотим воспроизводить ошибочный механизм сохранения и восстановления Android на Flutter: вы не можете сохранить все состояние в пакете из-за ограничения на размер транзакции.

На Android у вас есть несколько логик сохранения / восстановления состояния:

  • saveInstanceState разработчика, но вы не можете хранить внутри большие объекты, например список элементов
  • база данных, в которой можно хранить большие объекты
  • большинство виджетов также управляют своим состоянием
  • стек действий / фрагментов

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

На флаттере состояние разработчика - единственный источник истины.
Разработчики должны сохранять это состояние на диске при его изменении, не обращаясь к ограниченным механизмам собственной платформы.

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

Нижняя точка: есть возможности для улучшения на стороне Flutter, чтобы сделать сохранение / восстановление состояния менее шаблонным (для таких вещей, как Navigator). Но я не думаю, что он должен обеспечивать привязку к состоянию сохранения / восстановления платформы.

Конечно, вы не сможете легко сохранить позицию прокрутки, но кого это волнует.

Все? Я был бы очень разочарован, если бы приложение сбрасывало свой список каждый раз, когда я случайно инициировал изменение ориентации. :п

@Saketme FWIW, Flutter не сбрасывает позиции прокрутки при изменении ориентации.

Использование приложения Flutter с небольшой долей многозадачности должно быть забавным на устройстве Android Go! Прогресс теряется каждый раз, когда 1 ГБ ОЗУ или меньше оказывается под давлением

@Saketme : как сказал @ mravn-google, Flutter не привязан к изменениям конфигурации, например к действиям Android.
Единственное, что необходимо для управления состоянием, - это когда приложение убивает ОС, находясь в фоновом режиме.

@LouisCAD Конечно, сохранение и восстановление состояния важны.
Разработчики Flutter уже могут реализовать большую часть этой логики самостоятельно, и Flutter, вероятно, должен предоставить способы сделать ее менее шаблонной. Однако механизм, предлагаемый Android, ошибочен, и мы могли бы воспользоваться возможностью с Flutter, чтобы разработать что-то лучше, вместо того, чтобы взламывать Activity onSaveInstanceState / onRestoreInstanceState .

Каков текущий статус этой проблемы?

Рассмотрим случай приложения чата, где я набираю длинное сообщение в TextField .
Внезапно мне поступает телефонный звонок, и через некоторое время ОС закрывает приложение чата из-за нехватки памяти.
После телефонного звонка я возвращаюсь в приложение чата. Будет ли Flutter автоматически сохраняться и воссоздавать сообщение, которое я набирал? или приложение должно сохраниться и воссоздать его вручную?

Какие еще «состояния», кроме положения прокрутки и маршрута, обычно не контролируются разработчиками?

@nsreenath Любое чат-приложение должно сохранять черновик сообщений через каждые несколько символов и при переходе в фоновый

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

Очень просто, если они проверяют savedInstanceState == null (при условии, что они поместили хотя бы 1 элемент в onSaveInstanceState(bundle) ). Если статический логический флаг isFirstInit == false и savedInstanceState != null , то это после смерти процесса.

Затем они могут создать любой тип обратного вызова сериализации / десериализации для сложного состояния, которое может быть очищено автоматически при обнаружении savedInstanceState == null . Таким образом, им нужно только сохранить какое-то случайное логическое значение, но они могут управлять своей собственной системой сохранения.

Очень просто, если они проверяют значение savedInstanceState == null (при условии, что они помещают хотя бы 1 элемент в onSaveInstanceState (комплект)).

На iOS нет savedInstanceState и onSaveInstanceState . Но приложение все равно может быть убито ОС.

Рассмотрим типичный рабочий процесс:

  • Пользователь переходит на определенный экран внутри приложения.
  • Затем пользователь переключается на другое приложение
  • Внезапно мудрая ОС решает убить предыдущее приложение, чтобы сохранить память.
  • Пользователь возвращается к предыдущему приложению, но видит главный экран. Не тот экран, который был открыт, когда она решила переключить приложение.

Итак, какой в ​​настоящее время рекомендуемый способ сохранить историю экрана во Flutter?

PS Конечно, это не важно, если в вашем приложении всего один экран: smirk:

@Zhuinden Я не вижу многих сценариев, где это все равно нужно.

а) Пример чата:
Всегда сохраняйте и восстанавливайте предыдущее содержимое, написанное в текстовом поле, которое не было отправлено.

б) Пример полной формы:

  1. Всегда сохраняйте контент
  2. Когда пользователь вернется к экрану формы, сообщите ему, что есть черновая версия, которая была сохранена, но не отправлена, и спросить, хочет ли он ее восстановить. Не имеет значения, вышел ли пользователь из формы вручную (может спросить, хочет ли пользователь сохранить черновик) или приложение было убито в фоновом режиме.

Вероятно, есть некоторые случаи, когда конкретные знания о «новом экране» и «новом экране после смерти процесса» могут быть полезны, но это может быть не так часто, когда вы проектируете контент приложения, чтобы он всегда поддерживался базой данных.

@storix Вам необходимо вручную сохранить текущий маршрут и восстановить его при повторном запуске приложения. Я согласен, слишком много шаблонов для обычного использования. Но все же выполнимо.
Также: многие разработчики Android будут удивлены тем, как мало приложений / разработчиков iOS возьмутся за восстановление состояния. Большинство разработчиков, которых я знаю, даже не осознают, что это проблема. Думаю, это роскошь - работать только с устройствами более высокого класса.

@Zhuinden обратные вызовы жизненного цикла из https://github.com/flutter/flutter/issues/6827#issuecomment -335160555 работали для моего приложения чата. Я просто сохраняю какое-то состояние каждый раз, когда приложение переключается в фоновый режим. См. Также https://docs.flutter.io/flutter/dart-ui/AppLifecycleState-class.html.

Я хотел бы призвать команду Flutter не раскрывать методы жизненного цикла Android / iOS как канонический способ сохранения / восстановления состояния пользовательского интерфейса. Даже в нативных приложениях их было сложно обработать и правильно кодировать, а граница между пользовательским интерфейсом и состоянием приложения часто была размытой, и для каждого из них были предусмотрены разные механизмы. Было бы стыдно распространять все эти разрозненные API во Flutter (особенно, если в будущем будут нацелены на дополнительные платформы).

В виджетах Flutter без сохранения состояния _ уже_ была перенесена ответственность за состояние пользовательского интерфейса из виджетов в приложение, поэтому проблема сохранения / восстановления состояния пользовательского интерфейса все чаще решается за счет сохранения / восстановления состояния _app_. Я считаю, что это хорошо, так как в отличие от того, что пользовательский интерфейс и состояние приложения обрабатываются по-разному, как в iOS и Android.

Я заметил, что некоторые виджеты Flutter, такие как ScrollView и TextField, предоставляют классы «Контроллер», которые инкапсулируют динамическое состояние виджета. В некотором смысле они действуют как объекты-памятки - моментальные снимки состояния виджета - которые могут быть возвращены виджету позже, чтобы восстановить их состояние до некоторой предыдущей конфигурации. Звучит знакомо. Если бы эти «сувениры» было легко сохранять / восстанавливать, тогда приложение могло бы взять на себя ответственность за них как часть управления своим собственным состоянием. Это положило бы конец дихотомии состояния приложения / пользовательского интерфейса, позволило бы отказаться от использования фреймворка Flutter и избавило бы от необходимости предоставлять хуки сохранения состояния пользовательского интерфейса (меньше значит больше!)

Другими словами, возложите ответственность за это на разработчика, но постарайтесь сделать это как можно проще. Если разработчик считает, что наполовину заполненная форма достаточно важна, чтобы Flutter сохранялся при вызовах, то можно утверждать, что она, вероятно, достаточно важна, чтобы считаться состоянием приложения. Я понимаю, что сохранение состояния объектов приводит к проблемам с версией, но я думаю, что это более управляемая проблема.

Есть ли прогресс в этом вопросе? Я думаю, что существует реальная необходимость различать состояние процесса «первый запуск» и «восстановленное». Как еще можно запрограммировать отображение правого экрана, когда пользователь возвращается к убитому процессу?

@lukaspili, вы написали: «Единственное, что необходимо для управления состоянием, - это когда приложение убивает ОС, находясь в фоновом режиме».

Окей, тогда как насчет стека роутера, тоже сохранить на диск? Также я не использую t find proper solution to determine that application was restored. Every time when app goes to background I need to save router stack, app data, state to disc? What if I don t Redux?

Я думаю, что в качестве первого шага мы должны дать плагинам возможность сохранять и восстанавливать состояние. Вот мое предложение: # 22328

Были ли обновления по этой проблеме?

Я столкнулся с той же проблемой. У меня есть работа для сохранения текстовых вводов здесь через базу данных Firebase в реальном времени. Но этот метод довольно груб и часто не имеет возможности масштабирования, поскольку каждый пользователь будет перенаправлен в одну и ту же базу данных. Следовательно, мои данные доступны всем, у кого есть доступ к учетной записи Gmail. Я пробовал использовать Redis, но хотя я могу создавать отдельные списки в той же базе данных для других пользователей, это всегда делается с помощью метода POST. Это означает, что представление должно обновляться каждый раз, когда сохраняются новые данные. Что означает отсутствие визуальных эффектов в реальном времени. Действительно отличный глюк.

Здесь есть более серьезная проблема. Я имел некоторые назад и вперед с @ МЭТЬЮ-Carroll два месяца назад , и я совершенно уверен , что это продолжается.

Я начал делать библиотеку, которая занимается этим . Я бы на самом деле не рекомендовал его использовать, но это одна из идей реализации только для Dart. # 6225 немного усложнил задачу.

Я заметил, что приложение Google Реклама, которое, как сообщается, создано с помощью Flutter, действительно сохраняет состояние навигации после перезапуска активности. Было бы неплохо узнать, как это реализовано, даже если это обходной путь.

@aletorrado Сохраняет ли приложение Google Реклама состояние навигации в случае, если пользователь удалил приложение из «диспетчера задач» Android / IOS, а затем снова запустил его?

@gitspeak Нет, он сохраняет состояние только в том случае, если действие было автоматически прекращено ОС (возможно, с помощью ловушки onSaveInstanceState).

@aletorrado Так что это, наверное, единственный способ ...

Я действительно пытался войти в приложение Google Реклама, но у вас должна быть активная учетная запись, чтобы перейти на любой экран, кроме самого первого: D Я не могу проверить, действительно ли это работает или нет

Оно делает. Гарантированно. Он также сохраняет другое состояние, например положение прокрутки.

Чтобы прояснить некоторую путаницу:

  • Да, приложение Google Реклама написано на Flutter.
  • Поскольку они были одними из первых, кто принял решение, им пришлось сделать что-то необычное для фонового исполнения. Они создают и поддерживают свою основную изоляцию при запуске приложения (а не активности). Затем они передают этот изолят в представление Flutter при его создании.
  • То, что вы наблюдаете как «сохранение состояния», является побочным эффектом методологии, описанной выше. Когда вы убиваете активность в Google Ads, "UI" Flutter не умирает. Он хранится в памяти, привязанной к контексту приложения. Когда действие перезапускается, загружается тот же изолят и альт, и пользовательский интерфейс возвращается.

Мы не рекомендуем этот подход для решения этой проблемы, потому что он вообще не решает проблему. Если процесс приложения умирает, все состояние все равно теряется. Вы можете попробовать это с помощью Google Рекламы, перейдя и остановив процесс в разделе «Настройки»> «Приложения». Пользовательский интерфейс будет потерян.

Возможно, существует способ сохранить изолированное состояние локально, но он требует дополнительных исследований, поскольку мы не уверены, насколько это осуществимо / безопасно.

На данный момент решения, описанные выше, являются лучшими: подключитесь к собственным методам и выборочно сохраните состояние вашего приложения, чтобы вы могли восстановить его позже. До фреймворка, поддерживающего автоматическое решение как для iOS, так и для Android, еще далеко.

Вы можете попробовать это с помощью Google Рекламы, перейдя и остановив процесс в разделе «Настройки»> «Приложения». Пользовательский интерфейс будет потерян.

нет, это совершенно нормально. Принудительная остановка приложения должна потерять состояние.

Прерывание из-за нехватки памяти не должно.

В любом случае, если они сохранят состояние в приложении как одноэлементный объект в памяти, не сохраняя ничего на диске / связке, то оно БУДЕТ потеряно.

Жаль, что я не могу протестировать приложение без учетной записи, так как я не буду создавать учетную запись Google Рекламы только для того, чтобы повозиться с самим приложением. : Think_face:

Подключитесь к собственным методам и выборочно сохраните состояние вашего приложения, чтобы вы могли восстановить его позже.

Действительно, лучшим решением было бы сохранить активное состояние на диске и обнаружить смерть процесса на собственной стороне Android; если вы обнаружите смерть процесса, перезагрузите его с диска, в противном случае очистите его и перезапустите с нуля.

Я недостаточно знаю о навигаторах Flutter, чтобы сказать вам, можете ли вы восстановить историю навигации с помощью какого-либо метода setHistory или setBackstack как если бы вы использовали backstack, Conductor или что-то в этом роде.

Действия / фрагменты обрабатывают этот материал внутренне с помощью ActivityRecord / FragmentRecord, поэтому они не упоминаются сверх этого.

Спасибо за все, @mehmetf. Печально слышать, что поддержка сохранения / восстановления состояния еще далека от реализации.

Возможно, flutter_redux поможет сериализовать некоторые состояния приложения.

Хорошо, если вы сохраните blob-объект Redux store на диске. Только остерегайтесь проблемы "бесконечного диалога загрузки после смерти процесса". : Think_face:

Чтобы прояснить некоторую путаницу:

На самом деле я несколько больше запутался ..

Поскольку они были одними из первых, кто принял решение, им пришлось сделать что-то необычное для фонового исполнения. Они создают и поддерживают свою основную изоляцию при запуске приложения (а не активности). Затем они передают этот изолят в представление Flutter при его создании.

Как это связано с рассматриваемой проблемой? Изоляты Dart выполняются в отдельном процессе?

То, что вы наблюдаете как «сохранение состояния», является побочным эффектом методологии, описанной выше. Когда вы убиваете активность в Google Ads, "UI" Flutter не умирает. Он хранится в памяти, привязанной к контексту приложения. Когда действие перезапускается, загружается тот же изолят и альт, и пользовательский интерфейс возвращается.

Насколько я понимаю,

К вашему сведению: когда «Activity Dies» во время выполнения будет, ВСЕГДА следует завершение процесса.

Мы не рекомендуем этот подход для решения этой проблемы, потому что он вообще не решает проблему. Если процесс приложения умирает, все состояние все равно теряется. Вы можете попробовать это с помощью Google Рекламы, перейдя и остановив процесс в разделе «Настройки»> «Приложения». Пользовательский интерфейс будет потерян.

Как упомянул @Zhuinden, это недопустимый сценарий «сохранения состояния», так как приложение

Возможно, существует способ сохранить изолированное состояние локально, но он требует дополнительных исследований, поскольку мы не уверены, насколько это осуществимо / безопасно.

Это не способ сделать это. Для Android вы должны подключиться к механизму onSaveInstanceState, поскольку он был явно сделан для сохранения состояния Activity для тех сценариев, где требуется «сохранение состояния» после завершения процесса. Я не знаком с жизненным циклом приложения IOS.

На данный момент решения, описанные выше, являются лучшими: подключитесь к собственным методам и выборочно сохраните состояние вашего приложения, чтобы вы могли восстановить его позже. До фреймворка, поддерживающего автоматическое решение как для iOS, так и для Android, еще далеко.

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

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

Я вроде как описал, что мы можем сделать, но я недостаточно знаю о Flutter, чтобы сказать вам, поддерживает ли он то, что для него необходимо (возможность настраивать произвольную историю навигации в любой момент времени 😄)

@Zhuinden есть "миллион" крайних случаев, охватываемых onSaveInstanceState по отношению к действиям / элементам управления ... поверьте мне в этом ... но добро пожаловать для исследования в качестве учебного упражнения.

... и просто повторить свою позицию: задачи / действия Android, службы, жизненный цикл приложения (включая onSaveInstanceState) - все это часть контракта на программирование приложения, и поскольку код Flutter находится внутри процесса приложения Android, со стороны Flutter было бы неразумно обходить эти ограничения.

Уважаемые разработчики Android!

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

Почему я не могу использовать onSavedInstanceState ?

onSavedInstanceState требует, чтобы разработчики синхронно сохраняли состояние приложения. Вы должны немедленно вернуться. Но связь с Flutter по умолчанию асинхронная ( Future ). Поэтому вы не можете запросить сохраненное состояние из своего приложения Flutter, когда Android запрашивает его.
_Но вы могли бы вернуть сохраненное состояние, если бы вы создали его заранее, не так ли? _

Проблема не в том, что onSavedInstanceState недоступен, проблема в сроках. Когда Android просит сохранить состояние, вам не нужно данные.

Решение для восстановления A. Ваш собственный сохраненный файл состояния экземпляра

Вы можете прослушать onSavedInstanceState и сообщить своему приложению flutter об этом событии. Затем сохраните состояние вашего приложения и запишите его в file . Когда ваше приложение восстанавливается после смерти процесса (вы можете обнаружить это с помощью savedInstanceState на стороне Android), загрузите файл из файловой системы, проанализируйте его и восстановите свое состояние. (Подсказка: используйте каналы платформы, это проще, чем вы думаете).

Вы даже можете восстановить состояние, когда пользователи убили приложение, или отклонить сохраненное состояние, если оно слишком старое!

Решение для восстановления B: постоянно сохранять состояние

Мы могли бы запустить таймер и постоянно сохранять состояние приложения на стороне платформы в памяти. Скажем каждые 3 секунды. Когда Android затем вызывает onSavedInstanceState мы можем вернуть ранее сохраненное состояние. (Да, мы можем потерять изменения, произошедшие за последние 3 секунды)

Решение для восстановления C. Сохранение состояния в ключевых точках

Как и в решении B, сохраненное состояние будет храниться в памяти на стороне платформы. Но вместо того, чтобы сохранять состояние каждые несколько секунд, вы вручную запрашиваете сохранение состояния после того, как пользователи совершили какие-то действия. (открыл ящик, ввел текст в TextField , вы называете это).


Все 3 решения можно комбинировать, используйте то, что лучше всего подходит для вашего приложения.

Но как я могу сохранить состояние моего приложения?

В отличие от Android View s, Widget s не имеет обратного вызова onSaveInstanceState . Дерево виджетов не сохраняется автоматически. И это хорошо! Пользовательский интерфейс и состояние, наконец, разделены, и при восстановлении состояния представления или создании экземпляров Activity и Fragments нет никакой магии отражения.

Чтобы сохранить состояние, вам нужно перебрать каждый экран в своем приложении и сохранить всю информацию, которая требуется для их восстановления. Мне нравится делать это в json, но вы можете выбрать protobuf для лучшей производительности (измерьте, прежде чем оптимизировать!).

Глядя на наше любимое приложение для счетчика образцов, это может быть очень просто:

// complete app state of sample application
{
  "counter": 24,
}

Если у вас несколько экранов или диалогов, вы можете получить следующее состояние приложения json:

{
    "currentRoute": "/todo-detail/241",
    "routes": {
        "todo-detail/241": {
            "edited": "true",
            "title": "Buy bananas",
            "picture": "http://....",
            "show_confirm_delete_dialog": "false"
        },
        "todo-list": {
            "current_scroll_position": "365",
            "filter": "Filter.TODAY"
        }
    }
}

Фактическая реализация сильно зависит от архитектуры вашего приложения.

  • Если вы используете Redux, вы можете использовать промежуточное ПО для сохранения глобального состояния приложения на лету.
  • Если вы используете scoped_model этого может быть достаточно для сохранения моделей. Их воссоздание принципиально отличается от воссоздания состояния Redux.

Лучшие советы, которые я могу вам дать:

  • Сохраняйте строгое разделение вашего пользовательского интерфейса и ваших моделей.
  • Посмотрите на каждый экран и спросите себя, что вам действительно нужно для восстановления состояния. В большинстве случаев это действительно немного.
  • Поговорите со своим iOS colleagues и они скажут вам, что они не реализовали восстановление состояния через encodeRestorableState в течение многих лет. Android 1M + против iOS 974

Паскаль

Включите параметр «Не сохранять действия».

Нет, это не проверяет должным образом от смерти процесса. Этого не произойдет НИКОГДА, если вы явно не включите этот параметр. 😞

Если у вас несколько экранов или диалогов, вы можете получить следующее состояние приложения json:

{
  "currentRoute": "/todo-detail/241",
  "routes": {
      "todo-detail/241": {
          "edited": "true",
          "title": "Buy bananas",
          "picture": "http://....",
          "show_confirm_delete_dialog": "false"
      },
      "todo-list": {
          "current_scroll_position": "365",
          "filter": "Filter.TODAY"
      }
  }
 }

О боже, мы достигли «состояние навигации является частью состояния приложения как состояние конечного автомата», чтобы это работало, но мне нравится, как это выглядит 😄

О боже, мы достигли «состояние навигации является частью состояния приложения как состояние конечного автомата», чтобы это работало.

😂 не заставляйте меня заводить.

Остальная часть состояния приложения должна быть сохранена в базе данных / на сервере. Единственное переходное состояние, которое стоит сохранить в onSaveInstanceState, - это стек навигации и некоторые пользовательские данные. Это то же самое, о чем просят большинство людей в этой теме.

Остальная часть состояния приложения должна быть сохранена в базе данных / на сервере. Единственное переходное состояние, которое стоит сохранить в onSaveInstanceState, - это стек навигации и некоторые пользовательские данные. Это то же самое, о чем просят большинство людей в этой теме.

и это КЛЮЧ! (а также поддержка «Android Service»)

@gitspeak , последний обмен, который у нас не имел отношения к этой проблеме. Я скрою и то, и другое. Если вы хотите продолжить обсуждение, напишите мне в личку, используя мой дескриптор github @ gmail.com. Здесь явно есть недоразумение, выходящее за рамки данной проблемы.

Как один из разработчиков, работающих над интерфейсом между Flutter и Android, я хотел бы поблагодарить @passsy за отличный обзор проблем, связанных с сохранением состояния.

Я также хотел бы добавить, что мне интересно понять любые конкретные варианты использования, с которыми имеют дело разработчики в этом потоке. Состояние сохранения - это концепция, которая, вероятно, охватывает множество различных целей. То, что одно приложение называет состоянием, другое - нет. Одно приложение обязательно должно иметь гарантированное сохраненное состояние, в то время как другое просто предпочло бы сохранить какое-то состояние, но без него не сломается. Некоторые приложения полностью построены на Flutter, другие приложения должны синхронизировать информацию между традиционным пользовательским интерфейсом Android / iOS и Flutter.

Если кто-либо из вас хотел бы описать конкретный вариант использования, который ваше приложение должно поддерживать в отношении сохранения состояния, пожалуйста, напишите мне по адресу [email protected] - я активно

@passsy Пожалуйста, рассматривайте мои комментарии ниже как искреннюю попытку дать конструктивный отзыв

Решение для восстановления A. Ваш собственный сохраненный файл состояния экземпляра
Вы можете прослушать onSavedInstanceState и сообщить своему приложению flutter об этом событии. Затем сохраните состояние вашего приложения и запишите его в файл. Когда ваше приложение восстанавливается после смерти процесса (вы можете обнаружить это с помощью saveInstanceState на стороне Android), загрузите файл из файловой системы, проанализируйте его и восстановите свое состояние. (Подсказка: используйте каналы платформы, это проще, чем вы думаете).
Вы даже можете восстановить состояние, когда пользователи убили приложение, или отклонить сохраненное состояние, если оно слишком старое!

Несколько вопросов:

1) У этого подхода есть неотъемлемое состояние гонки. Поток с «сохранением состояния» вполне может пережить основной поток приложения Android, который вызывает события жизненного цикла, и в этом случае процесс может быть завершен во время записи файла. Вы можете попытаться скоординировать два потока, заблокировав в методе жизненного цикла сигнал завершения от потока «сохранения состояния», но это, помимо введения дополнительной сложности синхронизации, не может гарантировать правильность, поскольку достаточно длительная задержка может привести к ANR. (https://developer.android.com/topic/performance/vitals/anr)
Вы можете возразить, что «сохранение состояния» в файл происходит настолько быстро, что это вряд ли произойдет, но примите во внимание фактические задержки в планировании потоков и задержку хранилища, когда выполняются другие процессы, такие как фоновые загрузки, обновления магазина воспроизведения, медиаплеер и т. Д. Я не хочу обсуждать это, но мой личный опыт (разделяемый другими) показывает, что, по-видимому, устройства Android не застрахованы от того же «гриппа», которым заражены ПК с Windows - они также становятся медленнее со временем. Я совсем недавно вытер свой Nexus 7, и не только потребовались ЧАСЫ, чтобы загрузить и установить все приложения через Wi-Fi, но и отзывчивость устройства теперь является полной шуткой, когда я сомневаюсь, что у меня хватит терпения использовать его даже для поиска рецептов.

2) У вас не может быть надежного определения слова «слишком старый». Если я удалю приложение из диспетчера задач, я ожидаю, что все состояние немедленно исчезнет, ​​а если я переключусь на другое приложение, я ожидаю, что состояние будет сохраняться бесконечно, пока телефон включен. Дело в том, что Android не дает вам возможности различать эти два состояния.

3) Не все элементы управления пользовательского интерфейса раскрывают свое состояние через API, который позволяет пользователю настраивать элемент управления для определенного состояния. Такой API, несомненно, потребует дополнительных усилий при разработке со стороны разработчика средств управления. На Android ожидается, что элементы управления пользовательским интерфейсом с хорошим поведением будут отвлекать разработчика от проблем, связанных с «сохранением состояния», путем внутренней обработки уведомлений onSave / Restore InstanceState, тем самым контролируя, какое состояние будет сохранено. Если я правильно помню, элемент управления AutoComplete сохраняет термин запроса, но не список результатов.

Решение для восстановления B: постоянно сохранять состояние
Мы могли бы запустить таймер и постоянно сохранять состояние приложения на стороне платформы в памяти. Скажем каждые 3 секунды. Когда Android затем вызывает onSavedInstanceState, мы можем вернуть ранее сохраненное состояние. (Да, мы можем потерять изменения, произошедшие за последние 3 секунды)

Совершенно не согласен, помимо тех же проблем, связанных с «Решением А», он добавляет новый риск открытия банка червей для дублирования данных.

Решение для восстановления C. Сохранение состояния в ключевых точках
Как и в решении B, сохраненное состояние будет храниться в памяти на стороне платформы. Но вместо того, чтобы сохранять состояние каждые несколько секунд, вы вручную запрашиваете сохранение состояния после того, как пользователи совершили какие-то действия. (открыл ящик, ввел текст в TextField, вы его называете).

Имея те же проблемы, связанные с "Решением А".

Дерево виджетов не сохраняется автоматически. И это хорошо! Пользовательский интерфейс и состояние, наконец, разделены, и при восстановлении состояния представления или создании экземпляров Activity и Fragments нет никакой магии отражения.

Не согласен. Автоматическое сохранение Android дерева виджетов в тех случаях, когда его состояние может быть изменено , очень полезно! С какой стати вы хотите переложить эту ответственность на разработчика приложения? Почему это контрастирует с разделением UI-состояний? На самом деле, этот автоматический способ сохранения / восстановления дерева виджетов кажется прекрасным примером разделения пользовательского интерфейса и состояний.

«Решение A» кажется мне совершенно правдоподобным при использовании подходящих блокировок, и ANR не должен запускаться, если объем работы является разумным. Кроме того, используя механизм уведомлений платформы, он избегает постоянной ненужной работы по сериализации, которая может быть дорогостоящей при каждом нажатии клавиши.

Что касается возможности автоматизировать это поведение, мне кажется очевидным, что отсутствие механизма отражения в среде выполнения Dart и отсутствие структурированного способа хранения состояния в памяти во Flutter невозможно ни в коем случае. На данный момент самый простой подход, который я могу придумать для этого, - это отказаться от сохранения состояния в одном динамическом объекте (точно так же, как this.props и this.state в React).

@aletorrado

«Решение A» кажется мне совершенно правдоподобным при использовании подходящих блокировок, и ANR не должен запускаться, если объем работы является разумным.

Я согласен. когда ... Там, где модель Android гораздо более проста и надежна, поскольку она не требует, чтобы разработчик имел дело с блокировками, она не сохраняет переходное состояние на диск и сохраняет состояние только для тех случаев, когда это имеет отношение к чему-то, что вы (трепещут) не может обойтись без подключения к событиям жизненного цикла Android и функции onSaveInstance - для начала, для чего это нужно?

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

Нет необходимости сохранять каждое нажатие клавиши, а скорее представляет собой снимок, состоящий из выбранных состояний.

Данные должны храниться в Bundle как ByteArray, чтобы избежать
запуск любого ANR или задержки, вызванной вводом-выводом в основном потоке.

В среду, 19 декабря 2018 г., 18:08 Алехандро Торрадо [email protected]
написал:

«Решение А» кажется мне вполне правдоподобным при использовании подходящего
блокировки, и ANR не должно запускаться, если объем работы разумный.
Кроме того, используя механизм уведомлений платформы, он избегает выполнения
ненужная сериализация работает постоянно, что может быть дорогостоящим
для каждого нажатия клавиши.

Что касается возможности автоматизировать это поведение, мне кажется очевидным, что
не имеющий механизма отражения в среде выполнения Dart и не имеющий
структурированный способ хранения состояния в памяти во Flutter, что невозможно
с помощью любых средств. На данный момент самый простой подход, который я могу придумать, сделать
это означало бы отказ от сохранения состояния в одном динамическом объекте (точно так же, как
this.props и this.state на React).

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448671264 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AGpvBUQk17YOsjLMHTWcdJHL9rR71u6lks5u6nKGgaJpZM4KwSuX
.

Настоящая уловка заключается в создании этого ByteArray, о котором вы говорите: слегка_smiling_face:

Я бы сказал, что настоящая уловка состоит в том, чтобы восстановить эту
ByteArray правильно.

В среду, 19 декабря 2018 г., 18:41 Габор Варади [email protected] написал:

Настоящая уловка заключается в создании этого ByteArray, о котором вы говорите 🙂

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448682229 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AGpvBep8AW2xkKECt6uyTjqRGwri9Pmoks5u6npKgaJpZM4KwSuX
.

Нет. Когда у вас есть байтовый массив, это очень просто. Просто покажите индикатор загрузки, пока вы его загружаете (при условии, что вы сказали себе с каналами, что вам нужно его загрузить).

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

Из-за этой проблемы приложения Flutter не могут корректно интегрироваться в Android как
они работают некорректно, когда есть нагрузка на ОЗУ и пользователь переключает
приложения в этих условиях, прежде чем вернуться.

20 декабря 2018 г., 2:42 Габор Варади [email protected] написал:

Нет. Когда у вас есть байтовый массив, это очень просто. Просто покажи загрузку
индикатор, пока вы его загружаете (при условии, что вы сказали себе
каналы, которые вам нужно загрузить).

-
Вы получаете это, потому что вас упомянули.
Ответьте на это письмо напрямую, просмотрите его на GitHub
https://github.com/flutter/flutter/issues/6827#issuecomment-448827537 ,
или отключить поток
https://github.com/notifications/unsubscribe-auth/AGpvBR4rmSxelodAjZvcQ6kOwPd7yxKPks5u6ushgaJpZM4KwSuX
.

Некоторые из нас обсуждали это немного подробнее здесь: https://github.com/mehmetf/flutter/issues/1. Соответствующие моменты по этому вопросу:

  • Чтобы исправить (https://github.com/flutter/flutter/issues/6827#issuecomment-447488177): Google Реклама тестировался с включенным параметром «Не сохранять действия». В этом случае процесс приложения не умирает, поэтому мое объяснение на (https://github.com/flutter/flutter/issues/6827#issuecomment-447955562) остается в силе. Как отметили @gitspeak и @Zhuinden , это не лучший способ проверить сценарии, от которых мы защищаемся, поскольку на практике это не работает.

  • Всякий раз, когда приложение находится в фоновом режиме, View.onSaveInstanceState вызывается в потоке пользовательского интерфейса для каждого представления в иерархии, для которого включено сохранение. Итак, хорошее решение с точки зрения разработчика - это реализовать FlutterView . Чтобы предотвратить условия гонки с потоком пользовательского интерфейса (который вы не можете / не должны блокировать), этот метод не должен затрагивать приложение Flutter, поскольку нет синхронного способа сделать это.

  • Все, что сохраняет onSaveInstanceState, не может быть больше 1 МБ, поэтому о сохранении всего изолированного состояния не может быть и речи. Вместо этого мы, возможно, сможем найти способ разделить код, ресурсы и данные, содержащиеся в изолированной памяти, и выгрузить только сериализованную часть данных в пакет.

  • Когда представление позже восстанавливается, FlutterView создает изоляцию и распаковывает ранее сохраненную часть данных, если таковая имеется.

  • Когда приложение находится в фоновом режиме, состояние, по-видимому, должно перестать меняться на стороне Flutter. Всегда будут крайние случаи, поэтому было бы неплохо составить список лучших практик для приложений Flutter (учитывая как жизненный цикл, так и памяти ).

  • Я предполагаю, что это будет гораздо более насущной проблемой, когда дело доходит до развивающихся рынков, таких как Индия, где Flutter, как ожидается, будет работать на недорогих телефонах. Ходят слухи, что Android Go имеет гораздо более агрессивную стратегию управления памятью.

  • Они создают и поддерживают свою основную изоляцию при запуске _приложения_ (а не активности). Затем они передают этот изолят в представление Flutter при его создании.

вы можете привести пример этого кода?

Они создают и поддерживают свою основную изоляцию при запуске приложения (а не активности). Затем они передают этот изолят в представление Flutter при его создании.

вы можете привести пример этого кода?

Я не могу предоставить пример типа копирования / вставки, но вот что происходит:

заявка

  • В вашем Application.onCreate вызовите функции инициализации для FlutterMain . ( startInitialization и ensureInitializationComplete ). Вы можете найти ссылки на них в репозитории flutter / engine.
  • Создайте новый объект FlutterNativeView, передайте экземпляр Application в качестве контекста.
  • Запустите свой пакет в этом собственном представлении (поищите примеры runFromBundle ).
  • Реализуйте интерфейс в этом приложении, который позволяет вам возвращать это собственное представление (что-то вроде FlutterNativeViewProvider ).

Деятельность

  • Расширьте FlutterFragmentActivity и переопределите эти два метода:
  <strong i="26">@Override</strong>
  public FlutterNativeView createFlutterNativeView() {
    FlutterNativeViewProvider provider = (FlutterNativeViewProvider) getApplication();
    return provider.getFlutterNativeView();
  }

  <strong i="27">@Override</strong>
  public boolean retainFlutterNativeView() {
    return true;
  }

Что-то в этом роде должно вас подтолкнуть. Обратите внимание, что ваше приложение Flutter запустится до того, как станет доступным Activity (и, следовательно, окно). Поэтому вы не должны вызывать runApp() в своей основной функции, пока не получите сигнал от Activity, что она готова. Вы можете сделать это через PlatformChannels и одно место для сигнала, которое будет в createFlutterNativeView ().

Заявление об ограничении ответственности

  1. Как я упоминал выше, мы не совсем оправдываем этот шаблон, поскольку он на самом деле не решает эту конкретную проблему и довольно запутан.

  2. Обратите внимание, что многие из этих API-интерфейсов планируется изменить. Поскольку варианты использования add2app все еще развиваются, API встраивания Android и iOS еще не совсем стабильны. Как обычно, мы объявим о критических изменениях на flutter-dev @.

@passsy , @gitspeak

Решение для восстановления A. Ваш собственный сохраненный файл состояния экземпляра

Чтобы быть уверенным, что у вас есть действующий файл состояния, вы можете записать его в файл .tmp, а затем переименовать, когда запись будет завершена. Если вы также сохраните предыдущий файл состояния, вы всегда будете иметь действительное состояние. Также, когда процесс завершается при сохранении состояния.

@jsroest

у вас всегда будет действительное состояние.

Но не обязательно правильное состояние, и в этом вся суть ..

Также нет требования для сохранения состояния в энергонезависимой памяти с самого начала.

@gitspeak

у вас всегда будет действительное состояние.

Но не обязательно правильное состояние, и в этом вся суть ..

Также нет требования для сохранения состояния в энергонезависимой памяти с самого начала.

Во многих ситуациях согласованное состояние важнее, чем наличие «большинства последних значений». Примером этого являются реляционные базы данных, используемые с транзакциями. Что, на мой взгляд, тоже отличное место для хранения состояния.

Сохранение в энергонезависимой памяти имеет некоторые преимущества; Процесс также прерывается при сбоях телефона, замене батареи, сбоях оборудования, обновлении ОС, перезагрузках в целом и т. Д., Поэтому состояние сохраняется при сохранении в энергонезависимой памяти.

@jsroest Ваши аргументы действительны, но IMHO не применимы к этой конкретной проблеме, поскольку «состояние», на которое здесь ссылаются, является переходным состоянием пользовательского интерфейса (например, положение прокрутки, активный выбор (ы), временный ввод текста и т. д.). Имейте в виду, что восстановление этого типа данных требуется только в тех ситуациях, когда пользователь явно не отказался от «экрана», но пользовательский ввод все еще может быть потерян, например, когда пользователь переключается на другое приложение. Пользователь не ожидает восстановления переходного состояния при потере питания, так же как и пользователь не ожидает, что браузер прокрутит последнюю отображаемую веб-страницу до точного смещения до потери питания системы. Дело в том, что характер данных, которые считаются «переходным состоянием», важен, и здесь обсуждается (в частности, функция Android onSaveInsatnceState) об обработке этого типа данных.

@gitspeak
Пишу программы для предприятия. Например, программы, которые используются на складах сборщиками заказов, или другой пример в розничной торговле, где владелец магазина сканирует товары для заказа у своего поставщика. Мои клиенты ожидают, что приложение запустится с того места, где они его оставили, что бы ни случилось. Это включает в себя переходное состояние, но также и стек навигации. Не могли бы вы объяснить, почему это не идеально подходит для функции OnSaveInstanceState, как обсуждается здесь? Может быть, ожидания пользователей на потребительском рынке разные, но ИМХО можно использовать ту же логику для сохранения и восстановления состояния.

@jsroest

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

Извините, я не знаю, как создавать приложения для таких клиентов, возможно, кто-то еще может помочь ...

@jsroest Это отличный вопрос, но он выходит за рамки данной проблемы. Flutter ограничен тем, что может делать сама платформа, и, как упоминает @gitspeak , Android onSaveInstanceState предназначен для сохранения переходного состояния пользовательского интерфейса, когда жизненный цикл данных отличается от того, что вы хотите. Локальное хранилище или удаленное хранилище (если вы хотите, чтобы состояние сохранялось при удалении и на разных устройствах) дало бы вам то, что вы хотите, но у него есть предостережения, которые снова выходят за рамки этой проблемы.

Дополнительная рекомендуемая литература:

https://developer.android.com/topic/libraries/architecture/saving-states [Обратите внимание, например, как эта статья отделяет локальное хранилище от onSaveInstanceState]
https://developer.android.com/guide/topics/data/data-storage
https://firebase.google.com/docs/firestore/

@jsroest, если ваши клиенты ожидают, что приложение вернется туда, где оно было, даже если оно было принудительно остановлено, была очищена задача или была перезапущена ОС , то это что-то совершенно индивидуальное, а не то, о чем здесь говорят.

onSaveInstanceState сохранял состояние задачи для данной задачи, но оно было сброшено при очистке задачи, принудительной остановке или перезагрузке ОС.

@mehmetf Я думаю, что эта проблема действительно достигла уровня многословия, на котором может быть довольно сложно разобраться. С благословения @LouisCAD , я предлагаю вам закрыть эту проблему и открыть новую, которая начинается с вашего последнего резюме (возможно, несколько адаптированного для акцента на формулировку проблемы и объем) вместе с обратными ссылками на предыдущие обсуждения.

Я согласен, но оставлю это на усмотрение @ matthew-carroll; скорее всего, он будет владельцем этого выпуска.

Спасибо за ответ.

Я не хочу торопиться, но думаю, что недостаточно ясно описал то, что ищу. Я не в курсе всей терминологии, используемой здесь, в этой теме. Я вижу много общего между тем, что я хотел бы видеть во Flutter, и тем, что описывают @LouisCAD и @passsy .

Мои программы обычно состоят из следующих частей:

  • Бэкэнд для вывода данных из устойчивого состояния, когда пользователь завершил работу.
  • Локальная база данных sqlite для устойчивого состояния
  • В памяти структура данных, называемая «переменными» для переходного состояния, может быть сериализована в энергонезависимую память, чтобы она могла пережить процесс.

В структуре данных «переменные» есть только простые классы со свойствами, которые можно сериализовать. Важной частью этой структуры данных «переменных» является стек экрана. (Просто список). Верхний экран - это последний экран, с которым взаимодействовал пользователь. Тот, который ниже, - это тот, к которому пользователь переходит, нажимая «назад».

Я могу представить, что OnSaveInstanceState можно использовать на платформе Android для запуска сериализации этой структуры данных. Что-то похожее должно быть найдено на других платформах (iOS, будущее: win, mac, web), но, как предполагает @passy, другие ключевые моменты также могут вызвать это, и этого может быть достаточно.

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

Если все это складывается, структура данных десериализуется и становится доступной в памяти. Программа загружает экран, который находится в верхней части стека. Сам экран загружает свое переходное состояние из структуры данных. Теперь у нас есть программа, которая выживает в результате технологической смерти. (Или мне что-то не хватает? Это определенно работает в моих прошлых проектах на Windows Mobile, формах Xamarin и asp.net. Я думаю, что это также должно работать во Flutter).

Пример приложения, где SXXX обозначает экран с номером. Номер используется только для того, чтобы напомнить программисту, какие экраны более или менее связаны друг с другом.

S100 Main menu
  S200 Order pick
    S210 Select order
      S220 Confirm pick location
        S230 Pick nr of pieces
          S240 Report shortage
        S250 Scan dropoff location
    S300 Cycle count
      S210 XXXXXX
        S220 XXXXXX
      S230 XXXXXX
    S300 Reprint labels
      S310 XXXXXX

Пример структуры данных переменных

Class variables {
    Class AmbientVariables {
        ….
    }

    Class ScreenStack{
        List<string> ScreenStack;
    }

    Class Orderpick {
        int selected_order;
        string comfirmed_location;
        string nr_picked;
        ….
    }

    Class CycleCount {
        ….
    }

    Class ReprintLabels {
        ….
    }
}

Все, что я хотел бы увидеть во флаттере, вероятно, уже есть, за исключением воссоздания стека экрана, также известного как навигационный стек. Понятия не имею, как воссоздать это дерево объектов в памяти. Я также не уверен, стоит ли мне воссоздавать его. Я также могу создать собственный стек навигации и реализовать его во флаттере как одностраничное приложение. Так же, как и в предыдущих проектах. Но тогда я бы потерял много встроенных вещей из классов MaterialApp и Scaffold, где стрелка / кнопка назад является наиболее очевидной.

Я понимаю, что это не сохраняет автоматически все переходное состояние. Например, выбранные тексты, положение списков, положение конкретной анимации. Но, как программист, вы можете решить на каждом экране, что нужно сохранить. Хотя это именно то, что @LouisCAD пытается предотвратить, потому что нужен весь шаблонный код.

Должен ли я делать новый выпуск или это вписывается в эту тему?

Еще раз спасибо за отзыв, я очень признателен.

@jsroest Спасибо за подробное объяснение. Опубликуйте это на StackOverflow с тегом flutter. По сути, вы запрашиваете рекомендации о том, как структурировать и построить свои маршруты и как сохранить последний посещенный маршрут в постоянном хранилище.

Вы можете подумать, что решение этой конкретной проблемы решит и вашу проблему, но это неправда. Вы можете написать мне в личку на моем github handle @ gmail.com, если не понимаете почему.

Есть ли прогресс по этому вопросу?

@vanlooverenkoen теоретически, если вы сохраните список маршрутов навигатора И иерархию ключей в файле (и состояние виджетов с отслеживанием состояния) и очистите его, когда на собственной стороне Android вы получите savedInstanceState == null , тогда вы потенциально можете сделать это стойкий и восстанавливаемый, это не так-то просто.

@Zhuinden На самом деле это намного сложнее, чем просто сохранять маршруты и ключи. Связь между экранами осуществляется либо с помощью обратных вызовов, либо путем ожидания результата. Вам как-то придется восстанавливать и эти состояния. В противном случае вы получите что-то, что выглядит правильно, но не действует как таковое. Вы можете обойтись без восстановления обратных вызовов и тому подобного, используя форму связи с использованием пакетов данных, точно так же, как вы это делаете при использовании собственного Android (с намерениями), но это значительно снизит простоту использования Flutter. Пока нет реального решения этой проблемы, Flutter нельзя использовать для многих приложений, в основном тех, которые используются для сбора больших и сложных данных на основе форм. Интересно, почему команда Flutter не решает эту проблему открыто, ведь они наверняка должны знать об этом. Возможно, это выявит серьезный недостаток в архитектуре Flutter и покажет, что помимо ярких презентаций, производящих впечатление на руководство, система не подходит для реальных приложений.

Я слежу за этим с самого начала, но мне пришлось перечитать кучу сообщений, поскольку я потерял контекст.

Если команда Flutter считает, что должна быть новая, свежая проблема, с меньшим количеством наводнений и более ясными планами, у меня нет проблем с этим, и в результате этот вопрос будет закрыт.

Я создал плагин native_state, чтобы использовать механизмы Android и iOS для сохранения состояния приложения при завершении процесса.

Navigator уже может восстанавливать маршруты приложения через свойство initialRoute как оказывается, _ если_ вы следуете некоторым определенным правилам, которые я задокументировал и добавил примеры, и вы, вероятно, всегда захотите использовать плагин в сочетании с восстановлением маршрута (либо с помощью предоставленного SavedStateRouteObserver либо каким-либо другим механизмом).

Тао только что указал мне, что это четвертый по популярности вопрос в нашем исследовании прошлой осенью. Я думаю, что здесь выражено сочетание желаний пользователей (в более чем 100 комментариях!), И нам нужно будет сесть в какой-то момент и разделить их на отдельные элементы, над которыми мы можем принять меры.

@eseidelGoogle Я не согласен с вашей оценкой. ИМХО, здесь нет «сочетания выраженных желаний пользователей», а скорее отсутствует поддержка платформы Android, что вызывает различные проблемы программирования, возникающие из одной и той же основной проблемы.

Приложения Android имеют две основные темы:

1) Машина состояний процесса (также известная как события жизненного цикла).
2) Инструментарий пользовательского интерфейса.

Flutter выполняет (2), но не (1). Необходимо решить обе проблемы.

3 года...

Быстрое обновление: в настоящее время я активно работаю над поиском решений этой проблемы.

@goderbauer Чтобы убедиться, что это проблема не только Android, iOS имеет те же механизмы, хотя влияние на приложения iOS, вероятно, меньше. Я создал плагин, который позволяет сохранять состояние через механизмы ОС, на которые тоже стоит обратить внимание: https://github.com/littlerobots/flutter-native-state

@hvisser как вы рекомендуете сохранять состояние навигации? В навигаторе от Flutter нет метода getRoutes .

@hvisser Да, я смотрю на это для обеих платформ (и он должен быть достаточно гибким, чтобы подключаться и к другим платформам). Я удалил метку платформа-Android, чтобы было ясно, что это не проблема Android.

@hvisser как вы рекомендуете сохранять состояние навигации? В навигаторе от Flutter нет метода getRoutes .

@Zhuinden Навигатор уже поддерживает его (хотя я не думаю, что это хорошо известно). У меня есть пример того, как настроить его в проекте нативного состояния, но TL; DR

  • Вы должны использовать именованные маршруты
  • Маршруты должны быть иерархическими, например, /screen1/screen2 следует за /screen1 следует за /
  • Маршруты помещаются в стек навигации, когда вы устанавливаете свойство initialRoutes и ваши маршруты организованы таким образом.
  • Я _ думаю_ вам также понадобится GlobalKey для navigatorKey на MaterialApp но это может быть специфическим для моего случая.

Поэтому, если вы установите initialRoutes на /screen1/screen2 и ваши маршруты настроены правильно, то восстановление навигации также будет работать.

Еще один случай на старых устройствах с 2 сим-картами, когда вы включаете и выключаете режим полета, собственные приложения youtube и flutter перезапускаются # 49057
Видео прилагается к моему выпуску

Попробуйте отключить режим отладки флаттера. если включен режим отладки, приложение всегда перезагружается.

debugShowCheckedModeBanner: false,

Я работаю в трепете с 2018 года, может быть, с июня / июля, и узнал, что это проблема, через 2-3 месяца. Я не знал, что это стало такой большой проблемой. Я делал приложение для музыкального плеера, и если я нажимал кнопку «Назад», то флаттер разрушал каждое состояние, в то время как музыка продолжала играть в фоновом режиме. Чтобы решить эту проблему, я создал библиотеку
Добавьте плагин в свой проект в pubspec.yaml _ [Для новичков :)] _

dependencies:
  flutter:
    sdk: flutter

  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^0.1.2
  system_shortcuts:

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

Использование -

Независимо от того, на каком виджете вы находитесь и нажимаете кнопку «Назад», вы знаете, что приложение закроется (т.е. пользователь покинет приложение), добавьте WidgetsBindingObserver Mixin в класс состояния, как это

class _MusicPlayerState extends State<MusicPlayer>
    with WidgetsBindingObserver{
...
}

После этого переопределите метод didPopRoute и добавьте следующий код в метод

<strong i="21">@override</strong>
  Future<bool> didPopRoute() async {
    await SystemShortcuts.home();
  return true;
  }

_Давайте понять, как это работает_:
Приложение запущено >> Пользователь нажимает кнопку "Назад" >> Домой" нажата, а кнопка "Назад" нет >> Приложение переходит в фоновый режим, и состояние не разрушается НИ ОДИН ИЗ ЭТОГО

Рабочий образец -

_ РЕДАКТИРОВАТЬ _
_Это приложение написано с использованием управления состоянием поставщика, но не используется код для управления каким-либо состоянием после закрытия приложения и повторного открытия из последнего. Здесь работает только код, который мы добавили.

ezgif com-video-to-gif

ezgif com-video-to-gif (1)

Проблемы -

Если вы перешли в свое приложение из другого приложения, всякий раз, когда вы возвращаетесь с домашнего виджета, вы всегда будете переходить на главный экран, а не на то предыдущее приложение, откуда вы пришли.
Пример :-
Пользователь находится в WhatsApp (или любом другом приложении) >> Они получают уведомление от вашего приложения >> Они открывают ваше приложение из уведомления >> Теперь они нажимают кнопку "Назад" >> Они вернутся на главный экран телефона, а не WhatsApp (который должен быть сценарием по умолчанию)
_Эта проблема пока не представляла для меня большой проблемы, и я думаю, что все обычные пользователи и разработчики согласятся с ней, поскольку в редких случаях вы заходите в свое приложение из другого приложения. Я не забываю, что это может происходить тихо несколько раз, но это работает для меня, и это просто обходной путь, пока он не получит реальную помощь от основной команды flutter.

То, что этой проблеме уже почти 3 с половиной года, и до сих пор ничто так не огорчает.

Судя по установленной в настоящее время вехе, над этим планируется работать к маю 2020 года ... как раз вовремя для ввода-вывода Google?

hvisser выше решил проблему, теперь людям просто нужно написать код, который заставит его работать

Я добиваюсь прогресса в поиске решения для этого. Для предварительного просмотра того, как приложение сможет восстановить состояние экземпляра после этого, ознакомьтесь с этим PR: https://github.com/flutter/samples/pull/433.

К сожалению, пока все будет готово, потребуется немного больше времени.

Проектный документ с подробностями о том, как выполнить восстановление состояния во Flutter, доступен здесь: http://flutter.dev/go/state-resturation-design

PR с общей структурой восстановления состояния, как указано в схеме выше, был размещен: https://github.com/flutter/flutter/pull/60375. Я все еще работаю над добавлением еще нескольких тестовых покрытий.

Была представлена ​​общая структура восстановления состояния (https://github.com/flutter/flutter/pull/60375). Сейчас я работаю над интеграцией его в наши виджеты. Собираюсь начать с прокручиваемых, текстовых полей и навигатора.

Вы можете реализовать это самостоятельно, сохранив данные, которые вы хотите восстановить, в базе данных sqlite . Это метод, который используется при написании серверов.

Это дает возможность восстановить приложение, даже если в телефоне разрядился аккумулятор, произошла паника ядра или что-то еще. Я рекомендую создать объектно-ориентированную оболочку вокруг ваших вызовов SQLite. В Dart нет чего-то вроде SQLite.Net, который, к сожалению, делает это автоматически.

edit: Это также то, что мы делали на заре мобильной разработки, потому что память была настолько ограничена, а sqlite - отличный способ поменять местами между памятью и диском.

Я закрываю этот вопрос, так как общий фреймворк восстановления состояния (и встраивание Android) приземлился. Остальные работы отслеживаются в отдельных выпусках:

Отличная работа!!!!! Спасибо флаттер !!!

Была ли эта страница полезной?
0 / 5 - 0 рейтинги