30 мар. 2025 г.·7 мин

Паттерны фоновой синхронизации с Kotlin WorkManager для полевых приложений

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

Паттерны фоновой синхронизации с Kotlin WorkManager для полевых приложений

Что означает надёжная фоновая синхронизация для полевых и операционных приложений

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

Такие приложения сложнее обычных потребительских, потому что телефоны работают в худших условиях. Сеть прыгает между LTE, слабым Wi‑Fi и отсутствием сигнала. Режим экономии батареи блокирует фоновые задачи. Приложение убивается, ОС обновляется, и устройство перезагружается по маршруту. Надёжная конфигурация WorkManager должна пережить всё это без драмы.

Надёжность обычно означает четыре вещи:

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

«Запустить сейчас» подходит для мелких действий, инициированных пользователем, которые должны завершиться быстро (например, отправка одного статуса перед закрытием задачи). «Подождать» — для тяжёлой работы: загрузки фото, пакетных обновлений или всего, что может разрядить батарею или упасть в плохой сети.

Пример: инспектор отправляет форму с 12 фото в подвале без сигнала. Надёжная синхронизация сохраняет всё локально, помечает как в очереди и загружает позже при реальном соединении, без необходимости переделывать работу.

Выбор правильных строительных блоков WorkManager

Начните с выбора наименьшей, понятной единицы работы. Это решение влияет на надёжность больше, чем любые хитрые стратегии повторов.

OneTime vs Periodic

Используйте OneTimeWorkRequest для работы, которая должна выполняться из‑за изменившегося состояния: новая форма сохранена, фото завершило сжатие или пользователь нажал Sync. Поставьте её в очередь сразу (с ограничениями) и позвольте WorkManager выполнить, когда устройство готово.

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

Практичный паттерн: one‑time для «нужно синхронизировать скоро», а periodic как запасной вариант.

Выбор Worker, CoroutineWorker или RxWorker

Если вы пишете на Kotlin и используете suspend‑функции, предпочитайте CoroutineWorker. Код короче, и отмена ведёт себя ожидаемо.

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

RxWorker имеет смысл только если приложение уже активно использует RxJava — в противном случае это лишняя сложность.

Чейнить шаги или сделать один воркер с фазами?

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

Простое правило:

  • Чейните, когда шаги имеют разные ограничения (загрузка по Wi‑Fi, затем лёгкий API‑вызов).
  • Используйте один воркер, когда нужна поведение «всё‑или‑ничего».

WorkManager гарантирует, что работа персистится, переживает смерть процесса и перезагрузки и уважает ограничения. Он не гарантирует точного времени запуска, немедленного выполнения или запуска после форс‑стопа приложения. Если вы строите Android‑полевое приложение, проектируйте синхронизацию так, чтобы задержки были безопасными и ожидаемыми.

Сделайте синхронизацию безопасной: идемпотентной, инкрементальной и возобновляемой

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

Начните с того, чтобы каждый серверный вызов можно было выполнить дважды без побочных эффектов. Простейший подход — ключ идемпотентности для каждого элемента (например, UUID, сохранённый с локальной записью), который сервер трактует как «тот же запрос — тот же результат». Если нельзя менять сервер, используйте стабильный натуральный ключ и upsert‑эндпоинт или добавьте номер версии, чтобы сервер мог отвергать устаревшие обновления.

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

  • queued
  • uploading
  • uploaded
  • needs-review
  • failed-temporary

Держите синхронизацию инкрементальной. Вместо «синхронизировать всё» храните курсор вроде lastSuccessfulTimestamp или серверного токена. Читайте небольшую страницу изменений, применяйте их и продвигайте курсор только после того, как партия полностью сохранена локально. Небольшие батчи (20–100 элементов) сокращают таймауты, делают прогресс видимым и ограничивают работу, которую придётся повторить после прерывания.

Сделайте загрузки возобновляемыми. Для фото или больших payload'ов сохраняйте URI файла и метаданные загрузки и помечайте как загруженное только после подтверждения сервера. При рестарте воркер продолжит с последнего известного состояния, а не начнёт сначала.

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

Ограничения, соответствующие реальным условиям устройства

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

Начните с минимального набора, который защищает пользователей, но позволяет задаче запускаться большую часть дней. Практичная база: требовать сетевого подключения, не запускать при низкой батарее и не запускать при критически низком месте на диске. Добавляйте требование «зарядка» только если работа тяжёлая и не срочная — многие полевые устройства редко подключены к сети во время смены.

Слишком жёсткие ограничения — частая причина жалоб «синхронизация никогда не запускается». Если вы требуете unmetered Wi‑Fi, зарядку и отсутствие низкого заряда, вы фактически просите идеальный момент, который может никогда не наступить. Если бизнесу нужны данные сегодня, лучше запускать меньшие задачи чаще, чем ждать идеальных условий.

Каптивные порталы — ещё одна реальная проблема: телефон показывает подключение, но пользователю нужно нажать «Принять» на странице гостиницы или публичного Wi‑Fi. WorkManager не всегда может это корректно определить. Обращайтесь с этим как с обычным сбоем: попытка, быстрый таймаут и повтор позже. Также показывайте простое сообщение в приложении, например «Подключено к Wi‑Fi, но нет доступа в интернет», если вы можете это определить во время запроса.

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

  • Мелкие payload'ы (статусы, метаданные форм): любая сеть, батарея не низкая.
  • Большие payload'ы (фото, видео, оффлайн‑пакеты карт): по возможности unmetered сеть и подумайте про зарядку.

Пример: техник сохраняет форму с 2 фото. Отправьте поля формы по любой сети, а загрузку фото поставьте в очередь до Wi‑Fi или более подходящего момента. Офис быстро увидит задачу, а устройство не будет жечь мобильный трафик на фоне.

Повторы с экспоненциальным backoff, которые не раздражают пользователей

Deploy where your team runs
Разверните в AppMaster Cloud или в вашей AWS, Azure или Google Cloud среде.
Развернуть приложение

Повторы — это то место, где полевые приложения либо кажутся спокойными, либо сломанными. Выберите стратегию backoff, соответствующую ожидаемому типу ошибки.

Экспоненциальный backoff обычно самый безопасный по умолчанию для сети. Он быстро увеличивает время ожидания, чтобы не забивать сервер и не расходовать батарею при плохом покрытии. Линейный backoff может подойти для коротких временных проблем (неустойчивый VPN), но в слабой сети он слишком часто пытается повторить.

Принимайте решения о повторах на основе типа ошибки, а не просто «что‑то упало». Простое правило:

  • Таймаут сети, 5xx, DNS, отсутствие соединения: Result.retry()
  • Истёкший токен (401): обновите токен один раз, затем провал и попросите пользователя войти
  • Валидация или 4xx (плохой запрос): Result.failure() с понятной ошибкой для поддержки
  • Конфликт (409) для уже отправленных элементов: воспринимайте как успех, если синхронизация идемпотентна

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

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

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setBackoffCriteria(
    BackoffPolicy.EXPONENTIAL,
    30, TimeUnit.SECONDS
  )
  .build()

// in doWork()
if (runAttemptCount \u003e= 5) return Result.failure()
return Result.retry()

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

Видимый пользователю прогресс: уведомления, foreground‑работа и статус

Run a small field pilot
Сначала выпустите один надёжный срез — например, форму инспекции с фото и безопасными повторными попытками.
Запустить пилот

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

Когда требуется foreground‑работа

Используйте foreground‑выполнение, когда задача долг-running, чувствительна ко времени или явно связана с действием пользователя. На современных Android большие загрузки могут быть остановлены или отложены, если вы не запустите их как foreground. В WorkManager это значит вернуть ForegroundInfo, чтобы система показала постоянное уведомление.

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

Прогресс, которому можно доверять

Прогресс должен соответствовать реальным единицам, а не расплывчатым процентам. Обновляйте прогресс через setProgress и читайте его из WorkInfo в UI (или на странице статуса).

Если вы загружаете 12 фото и 3 формы, показывайте «5 из 15 загружено», указывайте, что осталось, и сохраняйте последнее сообщение об ошибке для поддержки.

Сделайте прогресс осмысленным:

  • Выполненные и оставшиеся элементы
  • Текущее действие ("Загрузка фото", "Отправка форм", "Финализация")
  • Время последней успешной синхронизации
  • Последняя ошибка (короткая, понятная пользователю)
  • Видимая кнопка остановки/отмены

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

Уникальная работа, теги и предотвращение дублирования задач

Дублирующие задачи синхронизации — один из простейших способов разрядить батарею, съесть мобильный трафик и создать конфликты на сервере. WorkManager даёт два простых инструмента: уникальные имена работы и теги.

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

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .addTag("sync")
  .build()

WorkManager.getInstance(context)
  .enqueueUniqueWork("sync", ExistingWorkPolicy.KEEP, request)

Выбор политики — основное решение о поведении:

  • KEEP: если синхронизация уже запущена (или в очереди), игнорировать новый запрос. Подходит для большинства кнопок «Sync now» и авто‑триггеров.
  • REPLACE: отменить текущую и начать заново. Подойдёт, когда входные данные действительно изменились (смена аккаунта, выбор другого проекта).

Теги помогают управлять и наблюдать. Со стабильным тегом sync можно отменять, запрашивать статус или фильтровать логи без отслеживания конкретных ID. Это особенно полезно для ручной кнопки «sync now»: можно проверить, запущена ли уже работа, и показать понятное сообщение, вместо того чтобы запускать ещё один воркер.

Периодическая и по‑требованию синхронизация не должны конфликтовать. Держите их отдельными, но скоординированными:

  • Используйте enqueueUniquePeriodicWork("sync_periodic", KEEP, ...) для запланированной работы.
  • Используйте enqueueUniqueWork("sync", KEEP, ...) для on‑demand.
  • В воркере быстро выходите, если нечего отправлять или получать, чтобы периодический запуск был дешёвым.
  • Опционально: периодический воркер может ставить в очередь тот же одноразовый уникальный sync, чтобы вся реальная работа происходила в одном месте.

Эти паттерны делают фоновые синхронизации предсказуемыми: одна синхронизация за раз, её легко отменить и наблюдать.

Пошагово: практичная конвейерная синхронизация

Add integrations the practical way
Подключайте аутентификацию, платежи, месседжи и интеграции AI без склейки десятков SDK.
Build Integrations

Надёжный pipeline синхронизации легче строить, если рассматривать его как простую машину состояний: элементы работы сначала живут локально, а WorkManager двигает их дальше, когда условия подходят.

Простой pipeline, который можно выпустить

  1. Начните с локальных таблиц очереди. Храните минимальные метаданные для возобновления: id элемента, тип (форма, фото, заметка), статус (pending, uploading, done), счётчик попыток, последняя ошибка и курсор или ревизию сервера для загрузок.

  2. Для «Sync now» от пользователя ставьте в очередь OneTimeWorkRequest с ограничениями, соответствующими реальному миру. Обычно это сеть подключена и батарея не низкая. Для тяжёлых загрузок требуйте зарядку.

  3. Реализуйте один CoroutineWorker с понятными фазами: upload, download, reconcile. Делайте каждую фазу инкрементальной. Загружайте только элементы в статусе pending, скачивайте изменения с момента последнего курсора, затем решайте конфликты простыми правилами (например: сервер побеждает для полей назначения, клиент — для локальных черновиков).

  4. Добавьте повторные попытки с backoff, но избирательно: таймауты и 500‑е должны повторяться, 401 должен сразу сообщать UI о проблеме.

  5. Наблюдайте WorkInfo, чтобы управлять UI и уведомлениями. Используйте обновления прогресса для фаз вроде «Загружается 3 из 10» и показывайте короткое сообщение об ошибке с указанием следующего шага (повтор, вход в систему, подключение к Wi‑Fi).

val constraints = Constraints.Builder()
  .setRequiredNetworkType(NetworkType.CONNECTED)
  .setRequiresBatteryNotLow(true)
  .build()

val request = OneTimeWorkRequestBuilder\u003cSyncWorker\u003e()
  .setConstraints(constraints)
  .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 30, TimeUnit.SECONDS)
  .build()

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

Частые ошибки и ловушки (и как их избежать)

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

Ловушки, за которыми стоит следить

  • Большие загрузки без ограничений. Если вы шлёте фото или большие payload'ы по любой сети и при любом заряде, пользователи почувствуют это. Добавьте ограничения по типу сети и по низкому заряду, и разбейте большую работу на меньшие части.
  • Бесконечные повторы при всех ошибках. 401, просроченный токен или отсутствующее разрешение — не временная проблема. Пометьте как жёсткую ошибку, покажите понятное действие (переавторизация) и повторяйте только истинно транзитные проблемы, как таймауты.
  • Случайные дубликаты. Если воркер может выполниться дважды, сервер получит двойные создания, если запросы не идемпотентны. Используйте стабильный клиентский ID для каждого элемента и делайте серверную логику таким, чтобы повторы были обновлениями, а не новыми строками.
  • Использование periodic для почти реального времени. Периодическая работа хороша для обслуживания, но не для «sync now». Для инициированной пользователем синхронизации ставьте одноразовую уникальную работу.
  • Отображение «100%» слишком рано. Завершение загрузки ≠ подтверждение сервера. Отслеживайте стадии (queued, uploading, server confirmed) и показывайте «Готово» только после подтверждения.

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

Быстрая чек‑лист перед релизом

Ship native apps, not demos
Создавайте нативные iOS и Android приложения с оффлайн‑дружественными потоками данных и понятными состояниями синхронизации.
Build Mobile

Перед выпуском тестируйте синхронизацию так, как реальные полевые пользователи её ломают: прерывистая связь, разряженные батареи и постоянные нажатия. То, что выглядит нормально на dev‑телефоне, может провалиться в реальных условиях, если планирование, повторы или отчётность о состоянии нарушены.

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

  • Нет сети, затем восстановление: Запустите синхронизацию с отключённой сетью, затем включите её. Убедитесь, что работа в очереди (не проваливается), и возобновляется позже без дублирования.
  • Перезагрузка устройства: Начните синхронизацию, перезагрузитесь посередине, затем откройте приложение. Проверьте, что работа продолжается или перепланируется, и UI показывает корректное состояние (не «зависла в синхронизации»).
  • Низкая батарея и мало мест: Включите энергосбережение, опустите заряд ниже порога, при возможности заполните хранилище. Убедитесь, что задача ждёт, когда нужно, а затем продолжается без бешеных повторов, когда условия улучшаются.
  • Многократные триггеры: Несколько раз нажмите «Sync» или вызовите синхронизацию с разных экранов. В итоге должна быть одна логическая синхронизация, а не куча параллельных воркеров, соревнующихся за одни и те же записи.
  • Пояснения при ошибках сервера: Смоделируйте 500‑е, таймауты и ошибки авторизации. Проверьте, что повторы делают backoff и останавливаются после капа, а пользователь видит «Не удаётся связаться с сервером, будет повторено» вместо общей ошибки.

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

Пример сценария: офлайн‑формы и загрузки фото в полевом приложении

One platform for the whole stack
Соберите бэкенд, веб‑приложение и нативные мобильные клиенты на одной платформе, чтобы модели и аутентификация оставались согласованными.
Create App

Техник приезжает на объект со слабой связью. Он заполняет форму офлайн, делает 12 фото и нажимает Submit перед уходом. Приложение сперва сохраняет всё локально (например, в локальную базу): одна запись для формы и по одной записи на фото с явными статусами PENDING, UPLOADING, DONE или FAILED.

При нажатии Submit приложение ставит в очередь уникальную задачу sync, чтобы не создать дубликаты при повторных нажатиях. Обычная схема — цепочка WorkManager, которая сначала загружает фото (большие, медленные), а затем отправляет полезную нагрузку формы после подтверждения вложений.

Синхронизация запускается только при подходящих условиях: сеть, ненизкий заряд, достаточно места. Если техник всё ещё в подвале без сигнала, ничего не съедает батарею в фоне.

Прогресс очевиден и дружелюбен. Загрузка идёт как foreground‑работа и показывает уведомление вроде «Загружено 3 из 12» с понятной кнопкой Cancel. При отмене работа останавливается, а оставшиеся элементы остаются в PENDING, чтобы позже можно было повторить без потери данных.

Повторы вежливы при нестабильной точке доступа: первая неудача повторяется скоро, но каждая следующая делает паузу длиннее (экспоненциальный backoff). Сначала это кажется отзывчивым, затем плавно уходит в режим ожидания, чтобы не жечь батарею и сеть.

Для ops‑команды выигрыш практичен: меньше дублированных отправок благодаря идемпотентности и уникальной очереди, ясные состояния ошибок (какое фото упало, почему и когда будет повтор), и доверие к тому, что «отправлено» значит «безопасно сохранено и будет синхронизировано».

Следующие шаги: сначала надёжность, потом расширение объёма синхронизации

Прежде чем добавлять новые возможности синхронизации, ответьте себе, что значит «выполнено». Для большинства полевых приложений это не «запрос отправлен». Это «сервер принял и подтвердил», плюс UI‑состояние, соответствующее реальности. Форма со статусом «Synced» должна оставаться такой после перезапуска приложения; упавшая форма должна показывать, что делать дальше.

Сделайте приложение доверительным, добавив небольшой набор сигналов, которые пользователи и поддержка могут видеть (и о которых можно спросить). Держите их простыми и консистентными на всех экранах:

  • Время последней успешной синхронизации
  • Последняя ошибка синхронизации (короткое сообщение, не стектрейс)
  • Элементы в очереди (например: 3 формы, 12 фото)
  • Текущее состояние синхронизации (Idle, Syncing, Needs attention)

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

Если вы также строите бэкенд и админ‑инструменты, генерация их вместе помогает сохранить контракт синхронизации стабильным. AppMaster (appmaster.io) может сгенерировать production‑готовый бэкенд, веб‑панель админа и нативные мобильные приложения, что помогает держать модели и авторизацию согласованными, пока вы решаете сложные вопросы синхронизации.

Наконец, запустите небольшой пилот. Выберите один end‑to‑end сценарий (например, «отправка инспекционной формы с 1–2 фото») и выпустите его с ограничениями, повторами и видимым прогрессом. Когда этот срез станет надёжным и предсказуемым, расширяйте функциональность по одному элементу за раз.

Вопросы и ответы

What does “reliable background sync” actually mean in a field app?

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

When should I use OneTimeWorkRequest vs PeriodicWorkRequest for sync?

Используйте одноразную работу (OneTimeWorkRequest) для всего, что запускается реальным событием: «форма сохранена», «фото добавлено» или пользователь нажал Sync. PeriodicWorkRequest — для обслуживания и как страховка, но не полагайтесь на него для критичных загрузок, потому что его расписание может меняться.

Which Worker type should I choose: Worker, CoroutineWorker, or RxWorker?

Если вы пишете на Kotlin и используете suspend‑функции, CoroutineWorker — самый простой и предсказуемый выбор, особенно для корректной отмены. Worker подходит для коротких блокирующих задач. RxWorker имеет смысл только если приложение уже активно использует RxJava.

Should I chain multiple workers or do everything in one worker?

Чейнинг воркеров хорош, когда шаги имеют разные ограничения или должны повторяться независимо (например, загрузка по Wi‑Fi, потом лёгкий API‑вызов). Один воркер с фазами удобен, если шаги разделяют состояние и должны выполняться транзакционно — «всё или ничего».

How do I stop retries from creating duplicate records on the server?

Сделайте каждую операцию создания/обновления безопасной для повторного выполнения: используйте ключ идемпотентности (например, UUID, сохранённый с локальной записью). Если изменить сервер нельзя, применяйте стабильные естественные ключи и upsert‑эндпоинты или версионность, чтобы сервер не создавал дубликаты.

How do I make uploads resumable if the app is killed mid-sync?

Сохраняйте явные локальные статусы: queued, uploading, uploaded, failed. Маркируйте элемент как завершённый только после подтверждения сервера и храните метаданные (URI файла, счётчик попыток), чтобы после краша или перезагрузки можно было продолжить загрузку, а не начинать сначала.

What constraints are a good default for field app sync jobs?

Хороший минимум ограничений для полевых устройств: требовать сети, избегать запуска при низком заряде батареи и при критически низком месте на диске. Требование «unmetered» или «зарядка» добавляйте только если работа тяжёлая и не требует срочности — иначе синхронизация может никогда не выполниться.

How should my app handle captive portals or “Wi‑Fi with no internet”?

«Подключено, но без интернета» воспринимайте как обычный сбой: таймаутите быстро, возвращайте Result.retry() и попытайтесь позже. Если можно детектировать это во время запроса, покажите простое сообщение, чтобы пользователь понимал, почему устройство кажется онлайн, но синхронизация не идёт.

What’s the safest retry strategy for spotty networks?

Для сетевых ошибок используйте экспоненциальный backoff: интервалы между повторными попытками растут, чтобы не бомбить сервер и не разряжать батарею в плохой зоне. Повторяйте таймауты и 5xx, прекращайте и сообщайте при 4xx (плохой запрос), при 401 обновляйте токен один раз, а при постоянной ошибке останавливайтесь.

How do I prevent “sync storms” and still show user-visible progress?

Запускайте синхронизацию как уникальную работу, чтобы несколько триггеров не создавали параллельных задач, и показывайте пользователю реальный прогресс для долгих операций. Для пользовательских длительных задач используйте foreground‑работу с уведомлением, показывающим реальные счёты и кнопку отмены.

Легко начать
Создай что-то невероятное

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

Попробовать AppMaster