Реализация синхронизации офлайн-данных с сервером

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.
Разработка и поддержка любых видов мобильных приложений:
Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Предлагаемые услуги
Показано 1 из 1 услугВсе 1735 услуг
Реализация синхронизации офлайн-данных с сервером
Сложная
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    445

Реализация синхронизации офлайн-данных с сервером

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

Delta sync vs Full sync

Самая очевидная реализация — при восстановлении сети запросить все данные заново. Работает на маленьких объёмах. При 10 000 записях каждый раз скачивать полный список — это и трафик, и время, и нагрузка на сервер.

Delta sync — клиент присылает lastSyncTimestamp, сервер возвращает только изменённые и удалённые записи с этого момента.

data class SyncRequest(
    val lastSyncTimestamp: Long,
    val clientId: String
)

data class SyncResponse(
    val serverTimestamp: Long,       // время ответа сервера
    val updated: List<ProductDto>,   // изменённые или новые
    val deletedIds: List<String>     // удалённые на сервере
)

На клиенте сохраняем lastSuccessfulSyncTimestamp в MMKV или SharedPreferences. При следующей синхронизации используем его как фильтр.

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

Архитектура SyncManager

class SyncManager(
    private val api: SyncApi,
    private val dao: ProductDao,
    private val pendingOpsDao: PendingOperationDao,
    private val prefs: SyncPreferences
) {
    suspend fun sync(): SyncResult {
        // 1. Отправляем накопленные offline-операции
        val pending = pendingOpsDao.getAll()
        if (pending.isNotEmpty()) {
            try {
                val uploadResult = api.uploadOperations(pending.map { it.toRequest() })
                // Удаляем только успешно обработанные
                pendingOpsDao.deleteByIds(uploadResult.processedIds)
                // Неуспешные остаются в очереди
            } catch (e: NetworkException) {
                return SyncResult.NetworkError
            }
        }

        // 2. Скачиваем изменения с сервера
        return try {
            val response = api.sync(
                SyncRequest(
                    lastSyncTimestamp = prefs.lastSyncTimestamp,
                    clientId = prefs.clientId
                )
            )

            dao.applyDelta(
                updated = response.updated.map { it.toEntity() },
                deletedIds = response.deletedIds
            )

            prefs.lastSyncTimestamp = response.serverTimestamp
            SyncResult.Success(
                updatedCount = response.updated.size,
                deletedCount = response.deletedIds.size
            )
        } catch (e: Exception) {
            SyncResult.Error(e)
        }
    }
}

applyDelta в транзакции — атомарно. Или применяем всё, или ничего:

@Transaction
suspend fun applyDelta(updated: List<ProductEntity>, deletedIds: List<String>) {
    upsertAll(updated)
    softDeleteByIds(deletedIds, System.currentTimeMillis())
}

Soft delete обязателен: не удаляем физически, ставим флаг is_deleted = true и сохраняем timestamp. Иначе при следующем delta sync мы снова «забудем» про это удаление.

Триггеры синхронизации

Синхронизацию запускаем в нескольких сценариях:

class SyncScheduler(
    private val workManager: WorkManager,
    private val syncManager: SyncManager,
    private val networkMonitor: NetworkMonitor
) {
    init {
        // Периодическая фоновая синхронизация
        val periodicSync = PeriodicWorkRequestBuilder<SyncWorker>(15, TimeUnit.MINUTES)
            .setConstraints(Constraints(requiredNetworkType = NetworkType.CONNECTED))
            .build()
        workManager.enqueueUniquePeriodicWork(
            "periodic-sync",
            ExistingPeriodicWorkPolicy.KEEP,
            periodicSync
        )
    }

    // При восстановлении сети — немедленная синхронизация
    fun observeNetworkAndSync() {
        networkMonitor.isOnline
            .filter { it } // только переход offline→online
            .distinctUntilChanged()
            .onEach { triggerImmediateSync() }
            .launchIn(applicationScope)
    }

    // При возврате в foreground
    fun onAppForeground() {
        val lastSync = prefs.lastSyncTimestamp
        val tooOld = System.currentTimeMillis() - lastSync > 5 * 60 * 1000L
        if (tooOld) triggerImmediateSync()
    }
}

На iOS аналог WorkManager — BGAppRefreshTask и BGProcessingTask. Фоновые задачи iOS выполняются по усмотрению ОС и ограничены по времени (30 секунд для appRefresh, до 3 минут для processing).

Синхронизация изображений и файлов

Бинарные данные — отдельно от метаданных. Синхронизируем список файлов (имена, URL, хэши), скачиваем файлы по отдельным запросам с приоритизацией:

class MediaSyncManager {
    suspend fun syncMedia(mediaList: List<MediaMeta>) {
        val toDownload = mediaList.filter { meta ->
            !fileCache.exists(meta.localPath) ||
            fileCache.getHash(meta.localPath) != meta.serverHash
        }

        // Скачиваем параллельно, но ограничиваем конкурентность
        toDownload.chunked(4).forEach { batch ->
            batch.map { meta ->
                async { downloadFile(meta) }
            }.awaitAll()
        }
    }
}

Чанки по 4 — не перегружаем соединение, при потере связи теряем максимум 4 файла из текущего батча.

Состояние синхронизации в UI

Пользователь должен видеть актуальность данных. Минимум: timestamp последней синхронизации. Лучше: иконка статуса (synced / syncing / sync error) рядом с данными, которые могут быть устаревшими.

При sync error — не блокировать UI. Показывать предупреждение, разрешать работу с локальными данными, предлагать повторить.

Полная реализация двусторонней дельта-синхронизации с очередью операций и обработкой конфликтов: 4–8 недель в зависимости от объёма данных и количества типов сущностей. Стоимость рассчитывается индивидуально.