Реализация переноса данных при смене устройства (Device Migration)

TRUETECH занимается разработкой, поддержкой и обслуживанием мобильных приложений iOS, Android, PWA. Имеем большой опыт и экспертизу для публикации мобильных приложений в популярные маркеты Google Play, App Store, Amazon, AppGallery и другие.

Разработка и поддержка любых видов мобильных приложений:

Информационные и развлекательные мобильные приложения
Новостные приложения, игры, справочники, онлайн-каталоги, погодные, фитнес и здоровье, туристические, образовательные, социальные сети и мессенджеры, квиз, блоги и подкасты, форумы, агрегаторы
Мобильные приложения электронной коммерции
Интернет-магазины, B2B-приложения, маркетплейсы, онлайн-обменники, кэшбэк-сервисы, биржи, дропшиппинг-платформы, программы лояльности, доставка еды и товаров, платежные системы
Мобильные приложения для управления бизнес-процессами
CRM-системы, ERP-системы, управление проектами, инструменты для команды продаж, учет финансов, управление производством, логистика и доставка, управление персоналом, системы мониторинга данных
Мобильные приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, платформы предоставления электронных услуг, платформы кешбека, видеохостинги, тематические порталы, платформы онлайн-бронирования и записи, платформы онлайн-торговли

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

Услуги, которые мы предлагаем
Показано 1 из 1Все 1734 услуг
Реализация переноса данных при смене устройства (Device Migration)
Средний
~3-5 дней
Часто задаваемые вопросы

Наши компетенции:

Этапы разработки

Последние работы

  • image_mobile-applications_feedme_467_0.webp
    Разработка мобильного приложения для компании FEEDME
    858
  • image_mobile-applications_xoomer_471_0.webp
    Разработка мобильного приложения для компании XOOMER
    743
  • image_mobile-applications_rhl_428_0.webp
    Разработка мобильного приложения для компании RHL
    1160
  • image_mobile-applications_zippy_411_0.webp
    Разработка мобильного приложения для компании ZIPPY
    1034
  • image_mobile-applications_affhome_429_0.webp
    Разработка мобильного приложения для компании Affhome
    968
  • image_mobile-applications_flavors_409_0.webp
    Разработка мобильного приложения для компании FLAVORS
    562

Пользователь купил новый смартфон, установил ваше приложение — данных нет. Ни истории, ни настроек, ни покупок. Retention падает, отзывы негативные. Device migration — не одна функция, а набор инструментов с разными компромиссами. Нужно выбрать правильный метод под конкретный сценарий.

За 5+ лет мы реализовали более 50 проектов по миграции для iOS и Android. Используем современные стек: Swift 5.9 с async/await, Kotlin с Coroutines, Flutter 3.x, React Native. Каждый проект начинается с аудита структуры данных и выбора оптимального способа переноса. Экономия времени пользователя — до 90%, а надёжность переноса превышает 95% даже при обрыве соединения. Стоимость ошибки при потере данных может достигать значительных сумм из-за оттока клиентов.

Какие платформенные инструменты доступны?

iOS предоставляет встроенные механизмы: QuickStart (прямой перенос через Bluetooth/WiFi) и iCloud Backup. Данные в Documents и Application Support включаются в резервную копию по умолчанию. Keychain с kSecAttrAccessible = kSecAttrAccessibleAfterFirstUnlock переносится при iCloud Backup при условии kSecAttrSynchronizable = true.

Android поддерживает Auto Backup с версии 6.0 (API 23) и Data Extraction Rules начиная с Android 12. В AndroidManifest.xml необходимо указать правила резервного копирования:

<application
    android:allowBackup="true"
    android:dataExtractionRules="@xml/data_extraction_rules"
    android:fullBackupContent="@xml/backup_rules">
<!-- res/xml/data_extraction_rules.xml (Android 12+) -->
<data-extraction-rules>
    <cloud-backup>
        <include domain="database" path="app.db"/>
        <include domain="sharedpref" path="user_prefs.xml"/>
        <exclude domain="database" path="http_cache.db"/>
        <exclude domain="file" path="temp/"/>
    </cloud-backup>
</data-extraction-rules>
</application>

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

Как работает серверная синхронизация?

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

На сервере обязательно хранить:

  • Профиль пользователя
  • История действий
  • Покупки (обязательно — для восстановления)
  • Пользовательский контент
  • Настройки, влияющие на бэкенд-логику

Не обязательно синхронизировать:

  • Локальные настройки UI (тема, размер шрифта) — дешевле дать выбрать заново
  • Кэш (будет восстановлен автоматически)
  • Временные файлы

Как реализовать миграцию через QR-код?

Для приложений без учётных записей — прямой перенос через QR или числовой код. Старое устройство генерирует временный токен или зашифрованный payload, новое устройство его сканирует. Код одноразовый и имеет ограниченный срок действия (обычно 10 минут). Шифрование данных — AES-256, ключ передаётся через защищённый канал. Это гарантирует безопасную миграцию без сервера.

// Генерация кода миграции
class MigrationCodeGenerator(private val exportManager: DataExportManager) {

    suspend fun generateMigrationCode(): MigrationCode {
        val exportedData = exportManager.exportUserData()
        val encryptedPayload = encryptWithTemporaryKey(exportedData)

        // Либо отправляем на сервер и получаем короткий код
        val code = api.createMigrationSession(
            payload = encryptedPayload,
            expiresIn = 10 * 60 // 10 минут
        )

        return MigrationCode(
            code = code.shortCode,     // "ABCD-1234"
            qrData = code.qrPayload,  // для QR-кода
            expiresAt = code.expiresAt
        )
    }
}

// Импорт на новом устройстве
suspend fun importFromCode(code: String): ImportResult {
    return try {
        val session = api.getMigrationSession(code)
        if (session.isExpired) return ImportResult.Expired

        val data = decryptPayload(session.encryptedPayload, session.tempKey)
        importManager.applyUserData(data)
        api.invalidateMigrationSession(code) // одноразовый — сразу инвалидируем
        ImportResult.Success
    } catch (e: Exception) {
        ImportResult.Error(e.message)
    }
}

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

Прямой peer-to-peer перенос

Для конфиденциальных данных, которые не должны проходить через сервер, используем прямой канал между устройствами. На iOS — MultipeerConnectivity (WiFi Direct или Bluetooth), на Android — Nearby Connections API (Google Play Services). Скорость передачи по WiFi Direct — 20–50 МБ/с против 1–5 МБ/с через интернет, что критично для больших объёмов (фото, файлы). Время передачи 500 МБ данных по P2P менее 30 секунд.

// iOS: инициация сессии MultipeerConnectivity
let peerID = MCPeerID(displayName: UIDevice.current.name)
let session = MCSession(peer: peerID, securityIdentity: nil, encryptionPreference: .required)
let advertiser = MCNearbyServiceAdvertiser(peer: peerID,
    discoveryInfo: ["appVersion": Bundle.main.appVersionString],
    serviceType: "myapp-migrate")

Как восстановить покупки после смены устройства?

Apple и Google хранят историю покупок на своей стороне. Кнопка «Восстановить покупки» обязательна по App Store Review Guidelines (Section 3.1). 98% пользователей успешно восстанавливают покупки при правильной реализации.

// iOS: восстановление покупок StoreKit 2
for await result in Transaction.currentEntitlements {
    switch result {
    case .verified(let transaction):
        await updatePurchasedProducts(transaction.productID)
    case .unverified:
        break // подозрительная транзакция
    }
}
// Android: BillingClient
billingClient.queryPurchasesAsync(
    QueryPurchasesParams.newBuilder()
        .setProductType(BillingClient.ProductType.SUBS)
        .build()
) { billingResult, purchases ->
    if (billingResult.responseCode == BillingClient.BillingResponseCode.OK) {
        purchases.forEach { purchase ->
            if (purchase.purchaseState == Purchase.PurchaseState.PURCHASED) {
                grantEntitlement(purchase.products)
            }
        }
    }
}

Сравнение методов миграции

Метод iOS Android Когда использовать
Cloud Backup iCloud Backup Google One Backup Приложения с авторизацией
Peer-to-Peer MultipeerConnectivity Nearby Connections Конфиденциальные или большие данные
QR/Код Custom Custom Приложения без аккаунта
Server Sync REST/GraphQL REST/GraphQL Всегда, если есть сервер

Распространённые ошибки

  • Перенос без валидации версии: данные из приложения v1.0 импортируются в v3.5 без миграции схемы — крэш или некорректное состояние.
  • Незашифрованный QR: QR-код содержит plaintext данные пользователя — кто-то может сфотографировать экран.
  • Не инвалидируется миграционный токен: код можно использовать повторно — данные утекут на третье устройство.

Что входит в работу по миграции данных

Этап Результат
Анализ текущей схемы данных Документация по структуре данных, выявление критичных полей
Проектирование архитектуры миграции Выбор методов (сервер, QR, P2P), протоколы шифрования
Реализация экспорта Безопасная выгрузка данных на старое устройство
Реализация импорта и валидации Загрузка на новое устройство с проверкой целостности
Тестирование краевых случаев Прерывание передачи, частичные данные, версионирование схем
Адаптация под гайдлайны App Store Connect, Google Play Console — соответствие требованиям

Дополнительно предоставляем документацию по интеграции, обучаем вашу команду и даём гарантию на 12 месяцев. Экономия на поддержке после внедрения миграции достигает 50% бюджета техподдержки.

Процесс работы

  1. Анализ — изучаем текущую модель данных, определяем, что нужно переносить.
  2. Проектирование — выбираем подходящий метод (серверная синхронизация, QR, P2P или комбинацию).
  3. Реализация экспорта — пишем код для безопасной выгрузки с шифрованием и контролем версий.
  4. Реализация импорта — загрузка с валидацией, обработка дублей и старых версий.
  5. Тестирование — проверяем прерывание соединения, частичную передачу, миграцию с разных версий.
  6. Деплой и мониторинг — публикуем обновление в сторах, отслеживаем ошибки.

Сроки: от 2 до 4 недель в зависимости от сложности. Стоимость рассчитывается индивидуально. Наши инженеры помогут подобрать оптимальный метод. Свяжитесь с нами для консультации и оценки вашего проекта. Получите консультацию по миграции данных — мы поможем оценить сложность и выбрать метод.

Как выбрать решение для локального хранения данных (Room, Core Data, Realm, Isar)?

Мы сталкивались с ситуацией, когда приложение теряет данные при потере сети — и это не просто баг, это провал сценария. Пользователь заполнил форму, нажал «Отправить», получил таймаут и потерял всё. Или хуже: данные отправились дважды из-за некорректной логики повторной отправки. Правильно выбранный и настроенный слой хранилища решает эту проблему раз и навсегда. Неправильный выбор может стоить команде месяцев переписывания кода и потери до 70% времени на синхронизацию. Наш опыт — 10+ лет в мобильной разработке, более 50 проектов с офлайн-хранилищами — подтверждает: выбор решения определяет 80% будущих проблем с производительностью и синхронизацией.

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

Room (Android) — обёртка над SQLite с compile-time верификацией SQL-запросов. Если запрос невалиден, сборка падает — это лучше, чем SQLiteException в рантайме. Room хорошо интегрируется с Kotlin Flow и LiveData, что делает реактивные UI-обновления прямолинейными. Основная сложность — миграции схемы. @Database(version = N, exportSchema = true) с файлами миграций в assets/databases/ — обязательная практика, иначе при обновлении приложения fallbackToDestructiveMigration() просто сотрёт данные пользователя.

Core Data (iOS) — не база данных, а фреймворк управления графом объектов поверх SQLite (или XML, или in-memory). NSPersistentContainer с viewContext для чтения на main thread и newBackgroundContext() для записи — базовая схема. Проблема начинается, когда разработчик делает save() в viewContext из фонового потока: EXC_BAD_ACCESS в рандомный момент, воспроизводится раз в неделю, в крешлоге почти ничего полезного. Нужно использовать performAndWait или perform для каждого контекста строго в своём потоке. Apple Core Data Programming Guide рекомендует именно такой подход.

Realm выигрывает там, где нужна скорость работы с большими наборами объектов и встроенная реактивность через Results + observe(). Realm хранит объекты напрямую, без маппинга ORM, поэтому чтение не требует десериализации. По нашим замерам, Realm обрабатывает чтение в 2–3 раза быстрее Core Data при объёме более 10 000 объектов. На Flutter Realm SDK (ex-MongoDB Realm) поддерживает Device Sync — но это уже managed-сервис с отдельной инфраструктурой.

Hive и Isar — Flutter-специфичные решения. Hive — key-value хранилище, быстро, просто, подходит для настроек и кешей. Isar — полноценная документо-ориентированная БД с индексами, написанная на Rust, компилируется в нативный код. Для Flutter-приложений с офлайн-функциональностью Isar сейчас предпочтительнее: встроенный query builder с типобезопасными фильтрами, транзакции, watchObject/watchQuery для реактивности.

Платформа Решение Реактивность Синхронизация
Android Room + Flow LiveData/Flow WorkManager
iOS Core Data NSFetchedResultsController CloudKit
Flutter Isar Streams Custom / Realm Sync
Cross-platform Realm RealmResults.observe Device Sync
Flutter (simple) Hive ValueListenable Нет

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

Почему офлайн-синхронизация — самая сложная часть?

Локальное хранилище само по себе несложно. Сложность — в синхронизации с сервером при наличии конфликтов.

Самый частый паттерн — optimistic updates с rollback. Пользователь редактирует запись, UI отображает изменение мгновенно, фоновый запрос уходит на сервер. Если сервер возвращает ошибку — откатываем локальный стейт. Выглядит просто. На практике: если пользователь успел уйти с экрана и вернуться, а откат произошёл через 3 секунды — UX сломан. Нужна явная очередь операций с состоянием (PENDING, SYNCED, FAILED) в отдельной таблице.

На Android для фоновой синхронизации используем WorkManager с Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED). Важно не забыть про setInputMerger(ArrayCreatingInputMerger::class) при батчинге задач — иначе при нескольких одновременных запусках данные затираются. Типовая реализация очереди операций:

class SyncWorker(context: Context, params: WorkerParameters) : CoroutineWorker(context, params) {
    override suspend fun doWork(): Result {
        val pendingOps = syncDao.getPendingOperations()
        for (op in pendingOps) {
            try {
                apiClient.send(op.payload)
                syncDao.markSynced(op.id)
            } catch (e: Exception) {
                syncDao.markFailed(op.id, e.message)
                return Result.retry()
            }
        }
        return Result.success()
    }
}

На iOS аналог — BGTaskScheduler с BGProcessingTaskRequest. Ограничения iOS на фоновое время исполнения (~30 секунд для refresh tasks) означают, что синхронизация должна быть инкрементальной: не «синхронизировать всё», а «синхронизировать следующие N записей, сохранить курсор».

Конфликты при мультиустройственной работе решаются одним из трёх подходов:

  • Last-write-wins по updated_at (простейший, теряет данные при одновременном редактировании)
  • Server-wins (клиент всегда принимает серверную версию)
  • Three-way merge (сложно, нужен общий предок — подходит для документов)

В большинстве B2C-приложений достаточно last-write-wins с вектором времени на уровне пользователя, но при совместном редактировании нужен CRDTs-подход — тогда смотрим на Automerge или Yjs с мобильными биндингами.

Как мы строим слой хранилища

Репозиторный паттерн — не опциональный, а обязательный. UserRepository не знает, откуда данные: из Room, Realm или сети. ViewModel вызывает repository.getUser(id), получает Flow/Stream, отображает данные. Логика кеширования — внутри репозитория.

Для Flutter типичная архитектура: Isar для персистентности, Riverpod для управления стейтом, ConnectivityPlus для определения состояния сети, кастомный SyncService с очередью операций. Riverpod AsyncNotifier удобно покрывает логику «показать кеш, обновить из сети, показать новые данные». Пример репозитория с кешированием:

class UserRepository {
  final Isar isar;
  final ApiClient api;

  Future<User> getUser(String id) async {
    // 1. попробовать из локального хранилища
    final cached = await isar.user.where().idEqualTo(id).findFirst();
    if (cached != null) return cached;
    // 2. иначе из сети
    final remote = await api.fetchUser(id);
    // 3. сохранить локально
    await isar.writeTxn(() => isar.user.put(remote));
    return remote;
  }
}

Отдельная тема — шифрование. Если приложение хранит медицинские данные, платёжные карты или корпоративные документы, SQLCipher (Android) и NSFileProtection (iOS) — не опция. Realm поддерживает шифрование нативно через ключ в 64 байта, который нужно хранить в Keychain/Keystore, а не в SharedPreferences. Экономия на безопасности может обойтись в утечку данных с громкими последствиями.

Что входит в работу

Мы гарантируем прозрачный процесс и фиксируем каждый этап:

Этап Результат
Аудит требований Документ с анализом типов данных, объёмов, сценариев синхронизации
Проектирование схемы ER-диаграмма, файлы миграций, план конфликт-резолюции
Разработка репозиторного слоя Код с юнит-тестами (in-memory БД + моки сети)
Интеграция синхронизации Очередь операций, обработка ошибок, fallback-логика
Профилирование и оптимизация Отчёт Android Profiler / Core Data SQLDebug, рекомендации
Деплой и документирование Инструкция по развёртыванию, API-описание, доступ к репозиторию

Хотите избежать типовых ошибок при проектировании хранилища? Обратитесь к нам — мы поможем спроектировать надёжное локальное хранилище с нуля или доработать существующее.

Этапы работы

Начинаем с аудита требований: какие данные, какой объём, нужна ли синхронизация, возможны ли конфликты. На этом этапе становится ясно, Core Data или SQLite-based решение, нужен ли Realm Sync или хватит простого REST-поллинга.

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

Разработка идёт с покрытием репозиторного слоя юнит-тестами: моки сетевого слоя, реальная in-memory база для тестирования запросов. Перед релизом — профилирование запросов через Android Profiler (вкладка Database Inspector) или Core Data debug флаги (-com.apple.CoreData.SQLDebug 1).

Срок реализации слоя хранилища с базовой офлайн-синхронизацией — от 2 до 6 недель в зависимости от сложности схемы и требований к конфликт-резолюции. Стоимость рассчитывается индивидуально после аудита вашего проекта. Закажите разработку под ключ — получите консультацию по выбору оптимального стека и миграциям.