Чтобы восстановить черновик в .NET MAUI после завершения процесса, сохраняйте его на диск во время редактирования и загружайте при новом запуске: одного обработчика возврата из фона недостаточно. Объекты страницы, ViewModel и singleton-сервисы существуют только внутри текущего процесса; после его завершения приложение создаёт их заново.
Типичный сценарий — заполнение заявки: пользователь переключается в другое приложение, чтобы уточнить данные, а операционная система освобождает память. При возвращении важно восстановить введённый текст, выбранную запись и шаг формы. Ниже разобрана схема для такого редактора: небольшой снимок состояния, последовательная запись и отдельные пути холодного запуска и возобновления.
Какие события действительно получает приложение
Если процесс сохранился в фоне, приложение продолжает работу с существующими объектами. Холодный запуск после завершения процесса требует заново создать сервисы и интерфейс, затем прочитать сохранённые данные. Одинаковая карточка в списке недавних приложений не позволяет пользователю различить эти ситуации.
В документации Microsoft Learn «App lifecycle» события жизненного цикла .NET MAUI описаны на уровне Window. Для редактора полезно следующее распределение обязанностей:
| Событие | Что оно означает | Действие редактора |
|---|---|---|
| Created | Нативное окно создано | Подготовить запуск восстановления после создания необходимых зависимостей |
| Deactivated | Окно потеряло фокус, но может оставаться видимым | При необходимости запросить дополнительное сохранение |
| Stopped | Окно больше не видно | Остановить ненужную работу экрана и запросить сохранение актуального снимка |
| Resumed | Окно возобновляет работу после Stopped | Проверить актуальность данных, сохранив текущий пользовательский ввод |
| Destroying | Нативное окно уничтожается | Освободить связанные с окном ресурсы и подписки |
Resumed не заменяет процедуру начальной загрузки: оно не возникает при первом запуске. Потеря фокуса тоже не равна уходу в фон — её может вызвать временный системный интерфейс. Поэтому обработчики должны допускать повторные вызовы без создания нескольких таймеров или подписок.
Не рассчитывайте на Destroying как на последнюю возможность записать данные. Android не гарантирует вызов onDestroy при завершении процесса системой; приостановленное приложение iOS также может быть завершено без удобного заключительного обработчика. Эти ограничения описаны в Android Developers «The activity lifecycle» и документации Apple о подготовке приложения к работе в фоне. Сохранение нужно завершать заранее, пока приложение выполняется.
Что включить в снимок черновика
Начните с пользовательского обещания: какие действия останутся сохранёнными после перезапуска? Для заявки это обычно текст, выбранный объект, текущий шаг и ссылки на уже сохранённые вложения. Видимость индикатора загрузки, открытая клавиатура и состояние выполняющейся команды в это обещание не входят.
Опишите отдельный DTO — объект только с данными. Не сериализуйте страницу или ViewModel целиком: вместе с полями формы они содержат команды, сервисы, подписки и другие объекты, которые следует создать заново. Связь модели представления с интерфейсом разобрана в руководстве по MVVM с CommunityToolkit.Mvvm; формат долговременного хранения лучше отделить от этой связи.
using System.Text.Json.Serialization;
public sealed record DraftSnapshot(
int SchemaVersion,
string OwnerId,
string DraftId,
string Subject,
string Body,
int Step,
long Revision);
[JsonSerializable(typeof(DraftSnapshot))]
internal partial class DraftJsonContext : JsonSerializerContext
{
}Это пример собственного формата приложения. SchemaVersion задаёт версию структуры файла, Revision — порядок изменений конкретного черновика. Эти значения решают разные задачи: обновление приложения может изменить схему, а каждый новый пользовательский ввод — увеличить ревизию.
OwnerId позволяет проверить принадлежность данных после повторного входа. DraftId связывает сохранённый текст с конкретным черновиком. После чтения проверьте обязательные поля и допустимость Step: успешная десериализация ещё не означает, что состояние подходит текущей форме.
Для вложений сохраняйте относительные имена файлов внутри управляемого приложением каталога. Доступность временного файла или ранее выбранного внешнего ресурса после перезапуска требует отдельной проверки. Если вложение нужно сохранить надолго, завершите его копирование в подходящее хранилище до фиксации ссылки на него в черновике.
Как сохранять данные во время работы
Preferences подходит для небольших настроек вроде выбранного режима отображения. Растущий документ удобнее хранить в отдельном файле: его проще версионировать, проверять и заменять целиком. Для локального черновика используйте каталог FileSystem.Current.AppDataDirectory. CacheDirectory не подходит для единственной копии пользовательского ввода, поскольку система может очистить кеш.
На каждом изменении формы создавайте актуальное состояние в памяти и увеличивайте ревизию. Небольшая задержка перед записью позволяет объединять последовательные нажатия клавиш. Однако такая задержка оставляет промежуток, в котором последние символы ещё не сохранены. Для явного действия «Сохранить» дождитесь завершения записи; при переходе между шагами формы также полезна контрольная запись.
Следующий метод сервиса сохранения показывает запись одного снимка. Его должен вызывать единственный последовательный исполнитель; обработка ошибок и выбор актуальной ревизии остаются обязанностью вызывающего кода.
using System.Text.Json;
using Microsoft.Maui.Storage;
static async Task WriteDraftAsync(
DraftSnapshot snapshot,
CancellationToken cancellationToken)
{
string path = Path.Combine(
FileSystem.Current.AppDataDirectory, "active-draft.json");
string temporaryPath = path + ".tmp";
string json = JsonSerializer.Serialize(
snapshot, DraftJsonContext.Default.DraftSnapshot);
await File.WriteAllTextAsync(
temporaryPath, json, cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
File.Move(temporaryPath, path, overwrite: true);
}Промежуточный файл позволяет сначала закончить запись нового содержимого, сохранив предыдущий основной файл на время этой операции. Размещайте оба файла в одном каталоге. Это не универсальная гарантия сохранности при отключении питания: свойства замены и долговечность записи зависят от платформы и файловой системы. Если приложению уже нужно транзакционное хранилище, черновик разумно записывать в него.
После прерванной записи файл с расширением .tmp может остаться. При восстановлении читайте основной файл; не выбирайте временный автоматически по времени изменения. Ошибку записи покажите как несохранённые изменения и оставьте актуальный текст в памяти. Индикатор «Сохранено» должен появляться только после успешного завершения записи соответствующей ревизии.
В каком порядке восстанавливать экран
Выделите координатор восстановления, который отвечает за один запуск процедуры для конкретного окна. Он читает снимок, проверяет его и передаёт готовые данные редактору. Конструктор страницы при этом создаёт интерфейс, а длительное чтение не блокирует главный поток.
- Создайте окно с простым состоянием загрузки, чтобы пустая форма не успела запустить автосохранение.
- Определите текущего пользователя и выберите только принадлежащее ему сохранение.
- Прочитайте основной файл, проверьте схему и выполните предусмотренное преобразование старого формата.
- Создайте модель представления и примените снимок при временно отключённой реакции автосохранения.
- Проверьте существование связанных объектов, затем откройте редактор и разрешите обработку пользовательских изменений.
Применение сохранённых значений часто вызывает те же уведомления свойств, что и ручной ввод. Без защиты начальная загрузка может записать частично заполненную форму поверх полноценного черновика. Завершите применение всего снимка, установите его ревизию и только затем включайте обычный цикл сохранения.
При обычном Resumed не перечитывайте старый файл поверх живой формы. В памяти могут находиться более свежие изменения. Возобновление должно проверить внешние зависимости и обновить подходящие справочники, сохранив редактируемые поля.
Заранее установите приоритеты запуска. Например, явное открытие другой заявки по входящей ссылке может иметь приоритет над автоматическим возвратом к последнему редактору. Сохранённый черновик при этом остаётся доступным отдельно. Логику перехода выполняйте после подготовки окна; особенности маршрутов разобраны в руководстве по навигации Shell.
Восстановление должно открывать возможность продолжить работу, не повторяя необратимые действия. Если процесс завершился во время отправки заявки, сохранённый флаг «отправляется» не доказывает ни успех, ни неудачу. Сначала выясните состояние операции у сервера, затем предложите допустимое действие.
Как исключить гонки и устаревшие записи
Последовательный доступ к файлу решает только часть задачи. Представьте: снимок ревизии 12 ожидает задержку автосохранения, а контрольная запись уже сохранила ревизию 13. Если старая операция позже получит доступ к файлу, она перезапишет свежие данные. Одна блокировка этого не предотвращает.
Исполнитель записи должен принимать снимки в контролируемом порядке и отбрасывать ревизии старше уже принятой актуальной. Вместо длинной очереди промежуточных состояний можно держать один ожидающий снимок и заменять его более свежим. Если во время записи пришли изменения, после её завершения сохраняется последняя ожидающая версия.
Снимок создавайте на потоке, которому принадлежит состояние формы; после этого передавайте неизменяемый объект исполнителю. Не читайте разные свойства ViewModel по очереди из фоновой задачи: пользователь может изменить их между чтениями, и файл получит сочетание значений, которого в форме не было.
Отделяйте отмену поиска или загрузки справочника от отмены сохранения черновика. Токен видимости страницы удобно отменять при уходе с экрана, но использование этого же токена для контрольной записи может отменить именно нужное сохранение. После возврата создавайте новые токены для новых операций.
При выходе из аккаунта сначала остановите приём новых записей, завершите или отмените текущую, затем очистите либо изолируйте данные прежнего владельца. Иначе позднее завершение задачи способно вернуть уже удалённый файл. При поддержке нескольких окон используйте отдельные идентификаторы редакторов и согласованный доступ к общему черновику.
Как проверить восстановление на устройстве
Проверяйте отдельно возвращение в существующий процесс и запуск нового. На Android установите приложение, запустите его без подключённого отладчика, заполните форму и дождитесь подтверждённого сохранения. Перейдите на домашний экран, затем выполните команду, подставив идентификатор пакета:
adb shell am kill com.example.draftappКоманда am kill завершает только процессы, которые система считает допустимым завершить. Убедитесь по журналу нового запуска, что процесс действительно сменился, затем вернитесь через список недавних приложений. Принудительная остановка и смахивание карточки — отдельные сценарии; они не заменяют такую проверку. Настройка Android «Не сохранять действия» проверяет уничтожение Activity, но сама по себе не доказывает завершение всего процесса.
На iOS отдельно проверьте обычный возврат из фона и повторный запуск после принудительного завершения. Второй сценарий проверяет чтение файла новым процессом, хотя не воспроизводит все условия системной выгрузки. Проводите проверки на физическом устройстве без отладчика; один успешный возврат из фона не подтверждает восстановление после завершения процесса.
Добавьте сценарии, которые выявляют ошибки порядка операций:
- После подтверждённого сохранения завершите процесс: должны восстановиться все подтверждённые поля и нужный шаг.
- Быстро измените текст и уйдите в фон: проверьте поведение последней неподтверждённой ревизии.
- Вернитесь без завершения процесса: свежий ввод не должен заменяться сохранённой ранее копией.
- Запустите приложение с повреждённым файлом: интерфейс должен предложить восстановление из доступной копии или создание нового черновика.
- Проверьте старую схему, отсутствующее вложение и смену аккаунта: каждый случай должен иметь явный результат.
Для диагностики достаточно записывать идентификатор запуска, ревизию, начало и результат сохранения, источник восстановления и причину отказа. Текст заявки в журнал не нужен. Практический критерий готовности — возможность объяснить для каждого сценария, какая ревизия восстановится и почему более старый снимок не сможет её перезаписать.
Частые вопросы
Достаточно ли зарегистрировать ViewModel как singleton?
Нет. Singleton сохраняет объект только в пределах текущего процесса. После завершения процесса контейнер зависимостей создаётся заново, поэтому черновик нужно загрузить из постоянного хранилища.
Нужно ли сохранять весь стек страниц?
Не обязательно. Для редактора часто достаточно идентификатора черновика, полей формы и шага. Приложение может заново построить подходящий путь к экрану, проверив доступность связанных данных.
Можно ли гарантировать сохранность каждого введённого символа?
Нельзя обещать это при отложенной записи: процесс может завершиться до её начала или окончания. Определите допустимый промежуток несохранённых изменений и подтверждайте сохранение только после завершения соответствующей записи.
Шифрует ли AppDataDirectory содержимое черновика?
Этот API предоставляет путь к каталогу и сам не выполняет шифрование. Защита файлов средствами ОС и резервное копирование зависят от платформы и настроек приложения; для чувствительных черновиков задайте отдельные требования к хранению.




