Skip to Content
Основные понятияТреслетная синхронизация

Tracelet Sync: сетевая история

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

Обновление Tracelet 3.2.0: Логика синхронизации HTTP перенесена в модуль tracelet_sync. Вы должны включить этот модуль, если вам требуется сетевая синхронизация.


Как синхронизировать с сетью

В то время как Tracelet Core фиксирует местоположения, tracelet_sync — это фоновый механизм HTTP, который доставляет их на ваш сервер. Чтобы включить его, инициализируйте TraceletSync.ready() перед началом отслеживания:

import 'package:tracelet_sync/tracelet_sync.dart'; void main() async { WidgetsFlutterBinding.ensureInitialized(); // 1. Configure the Sync Engine await TraceletSync.ready(SyncConfig( url: 'https://your-api.com/locations', method: 'POST', autoSyncThreshold: 10, // Sync every 10 locations autoSyncDelay: 10000, // Wait 10s before pushing syncInterval: 0, // (Optional) Seconds between repeating queue flushes; 0 = off batchSync: true, // Send as a JSON array maxBatchSize: 250, // Up to 250 locations per request headers: { 'Authorization': 'Bearer YOUR_TOKEN' }, )); // 2. Configure and start Tracelet Core as usual await Tracelet.ready(Config.balanced()); await Tracelet.start(); }

После настройки движок будет автоматически обрабатывать все приведенные ниже сценарии!


Сценарий 1: Поход в горы

Изученные концепции: Очередь в автономном режиме, пакетная синхронизация, устранение дребезжания.

Проблема

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

Как Tracelet Sync решает эту проблему

  1. Сохранение SQLite в автономном режиме (autoSyncThreshold) Поскольку у Марка нет сигнала, Tracelet немедленно прекращает попытки отправлять HTTP-запросы. Вместо этого каждое местоположение надежно хранится в локальной базе данных SQLite. Мы установили autoSyncThreshold: 100, что означает, что Tracelet даже не пытается разбудить сетевое радио, пока в базе данных не будет поставлено в очередь как минимум 100 точек.

  2. Устранение дребезга синхронизации (autoSyncDelay) Когда Марк наконец съезжает с горы и восстанавливает 4G, у него внезапно оказывается 500 мест в очереди. Вместо того, чтобы немедленно отправить 500 HTTP-запросов (что привело бы к зависанию его телефона), autoSyncDelay: 10000 сообщает Tracelet подождать 10 секунд. Он препятствует быстрому притоку данных, позволяя стабилизировать соединение.

  3. Пакетная синхронизация (batchSync и maxBatchSize) Вместо 500 отдельных запросов POST batchSync: true и maxBatchSize: 250 объединяют местоположения в два массивных массива JSON. Он отправляет первые 250 точек, ждет, пока ваш сервер вернет HTTP 200 OK, удаляет эти точки из SQLite, а затем отправляет следующий пакет.

  4. Интервальная синхронизация (syncInterval) autoSyncDelay реагирует на новые локации. Если вам также нужна очистка по времени — загрузка всего, что находится в очереди, с фиксированной частотой, независимо от того, сколько очков накоплено — установите syncInterval на количество секунд между сбросами (например, syncInterval: 60 очищает автономную очередь раз в минуту). Он работает параллельно с устранением дребезга и отключен по умолчанию (0).


Сценарий 2. Подключение к Wi-Fi в кафе.

Изученные концепции: Сотовые ограничения, дельта-сжатие.

Проблема

Марк заканчивает поход и идет в кафе. Он путешествует по всему миру, поэтому его тарифный план сотовой связи чрезвычайно дорог. Ваше приложение поставило в очередь мегабайты данных о местоположении в формате JSON, и отправка их через роуминговое соединение 4G будет стоить ему денег.

Как Tracelet Sync решает эту проблему

  1. Ограничение сотовой связи (disableAutoSyncOnCellular) Установив disableAutoSyncOnCellular: true, Tracelet полностью блокирует механизм синхронизации, пока Марк находится в сети 4G. Местоположение остается в безопасности в SQLite. В тот момент, когда он подключается к Wi-Fi кафе, операционная система пробуждает Tracelet, и механизм синхронизации автоматически очищает очередь.

  2. Сжатие дельта-кодирования (enableDeltaCompression) Даже по Wi-Fi отправка огромных массивов JSON происходит медленно. Tracelet применяет Дельта-сжатие перед отправкой пакета. Если Марк шел по прямой, его широта не сильно менялась. Вместо отправки полных координат для каждой точки Tracelet отправляет первую точку полностью, а затем только разницу (дельту) для последующих точек. При использовании deltaCoordinatePrecision: 5 (с точностью до ~1,1 метра) размер полезной нагрузки HTTP уменьшается на 60–80 %.

    Стандартная полезная нагрузка (без сжатия):

    [ {"lat": 37.774900, "lng": -122.419400}, {"lat": 37.774910, "lng": -122.419410}, {"lat": 37.774920, "lng": -122.419420} ]

    Полезная нагрузка в дельта-кодировании (то, что получает ваш сервер):

    [ {"lat": 37.774900, "lng": -122.419400}, {"dLat": 10, "dLng": 10}, {"dLat": 10, "dLng": 10} ]

Сценарий 3: Истекшая сессия

Изученные концепции: 401 повторная попытка, безголовые обратные вызовы, экспоненциальная задержка.

Проблема

Срок действия токена авторизации Марка (JWT) истек, пока он был в походе. Когда Tracelet наконец пытается синхронизировать пакет с вашим сервером, ваш API возвращает HTTP 401 Unauthorized. Наивный механизм синхронизации либо удалит данные, думая, что это не удалось, либо застрянет в бесконечном цикле из 401 с, разряжая батарею. Телефон Марка лежит в кармане с выключенным экраном — он не может сейчас войти в систему.

Как Tracelet Sync решает эту проблему

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

    Обратный вызов на переднем плане:

    tl.Tracelet.setHeadersCallback(() async { final newJwt = await AuthAPI.refreshToken(); return {'Authorization': 'Bearer $newJwt'}; });

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

    @pragma('vm:entry-point') void headlessHeadersCallback(tl.HeadlessEvent event) async { final newJwt = await AuthAPI.refreshToken(); tl.Tracelet.setDynamicHeaders({'Authorization': 'Bearer $newJwt'}); } // Register it before runApp() tl.Tracelet.registerHeadlessHeadersCallback(headlessHeadersCallback);
  2. Экспоненциальный откат (maxRetries и retryBackoffCap) Что, если ваш сервер аутентификации не работает и возвращает 503? Tracelet прекрасно с этим справляется. Он пытается повторить попытку. Это терпит неудачу. Он ждет 1 секунду (retryBackoffBase), затем 2 секунды, затем 4 секунды. Экспоненциальная задержка ограничена 60 секундами (retryBackoffCap). После трех попыток (maxRetries) он полностью сдается, оставляя данные в безопасности в SQLite, чтобы повторить попытку завтра.


Сценарий 4. Схема пользовательского сервера

Изученные концепции: Custom Sync Body Builders, сопоставление схем.

Проблема

У компании Марка есть устаревшая серверная часть, которая принимает данные о местоположении в очень специфическом, нестандартном формате. Полезная нагрузка JSON Tracelet по умолчанию не соответствует требуемой схеме их сервера, и они не могут изменить внутренний API только для этого приложения.

Как Tracelet Sync решает эту проблему

  1. Custom Sync Body Builder (setSyncBodyBuilder) Вместо использования оболочки JSON по умолчанию Tracelet позволяет перехватывать пакет местоположений непосредственно перед их отправкой по сети, позволяя сопоставлять их в любой форме, которую желает ваш сервер.

    Начиная с Tracelet 3.2.8, местоположения, передаваемые в этот построитель, используют надежную вложенную схему (где координаты безопасно группируются в coords, а данные о деятельности - в activity).

    Tracelet.setSyncBodyBuilder((context) async { // Map Tracelet's nested schema to your legacy server's flat schema final mappedPoints = context.locations.map((loc) { final coords = loc['coords'] as Map; final activity = loc['activity'] as Map; return { 'lat': coords['latitude'], 'lng': coords['longitude'], 'time': loc['timestamp'], 'moving': loc['is_moving'], 'action': activity['type'], }; }).toList(); // Return the exact JSON structure your server expects return { 'device_id': myDeviceId, 'payload': mappedPoints, }; });
  2. Безголовая казнь Так же, как и обновление токенов, это создание пользовательского тела также может выполняться полностью в фоновом режиме через registerHeadlessSyncBodyBuilder(), гарантируя, что ваша пользовательская схема будет построена и отправлена ​​даже после полного закрытия приложения.


Сценарий 5: Водитель доставки

Изученные концепции: контекст маршрута и внедрение бизнес-логики

Проблема

Ваш сервер получает тысячи необработанных координат. Но одна только координата не скажет вам, почему пользователь был там. Был ли водитель занят доставкой? Какой заказ они доставили? Вам нужен способ прикрепить бизнес-логику непосредственно к полезной нагрузке фонового местоположения, чтобы вы могли легко запрашивать ее в своей базе данных.

Как Tracelet Sync решает эту проблему

  1. Настройка контекста маршрута Вы можете внедрить собственные метаданные в Tracelet. Каждое местоположение, записанное после вызова setRouteContext(), будет автоматически помечено этими данными во внутренней базе данных SQLite.

    await tl.Tracelet.setRouteContext( const tl.RouteContext( taskId: 'delivery-1234', driverId: 'john_doe', custom: {'shift_id': 'morning-shift-001'}, ), );
  2. Результирующая полезная нагрузка JSON Когда Tracelet синхронизируется с вашим сервером, каждое место в массиве будет включать объект context, гарантируя, что ваш сервер точно знает, какой задаче принадлежит это местоположение.

    { "locations": [ { "coords": { "latitude": 37.7749, "longitude": -122.4194 }, "battery": { "level": 0.85, "isCharging": true }, "extras": { "your_custom_key": "your_value" }, "context": { "taskId": "delivery-1234", "driverId": "john_doe", "custom": { "shift_id": "morning-shift-001" } } } ] }

Отслеживание состояния батареи

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

Для этого требуется нулевая конфигурация:

  • Механизм фиксирует состояния level (например, 0,85 для 85%) и isCharging.
  • Это состояние надежно сохраняется в автономной базе данных SQLite вместе с координатами GPS.
  • Когда устройство снова подключается к сети, механизм синхронизации передает точное историческое состояние батареи, а не текущее состояние батареи.

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

  1. Очистка контекста Когда водитель закончит доставку, очистите контекст. Последующие местоположения больше не будут отмечены тегами.

    await tl.Tracelet.clearRouteContext();

    Вы даже можете запросить внутреннюю базу данных SQLite на основе этого контекста перед синхронизацией:

    // Get all locations belonging to a specific delivery task final locations = await tl.Tracelet.getLocations( tl.SQLQuery(where: "context_task_id = 'delivery-1234'") );

Сценарий 6: Серверная часть телематики

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

Проблема

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

Как Tracelet Sync решает эту проблему

  1. Подпишитесь с помощью syncTelematics События вождения и ударов всегда сохраняются локально — это то, что читает Tracelet.getTelematicsEvents(). Их загрузка по умолчанию включена и отключена, поэтому полезная нагрузка существующей интеграции никогда не меняется под ней.

    http: tl.HttpConfig( url: 'https://api.example.com/locations', method: tl.HttpMethod.post, autoSync: true, batchSync: true, maxBatchSize: 50, syncTelematics: true, // ← upload driving/impact events too ),
  2. Один запрос, одно пробуждение по радио (по умолчанию) При использовании syncTelematics: true и отсутствии telematicsUrl события обрабатывают запрос местоположения в виде массива telematics корневого уровня, расположенного рядом с массивом location. Сброс остается одним POST — фоновое радио просыпается один раз, а не дважды.

    { "location": [ /* ...your usual location records... */ ], "telematics": [ { "id": 412, "event_type": "harsh_braking", "severity": 0.82, "speed": 18.4, "value": 0.47, "latitude": 24.8607, "longitude": 67.0011, "timestamp": "2026-08-20T09:14:02.000Z", "synced": false } ] }
    ПолеТипЗначение
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕПервичный ключ локальной строки SQLite. По возрастанию за установку — используйте его для дедупликации на своей стороне.
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕharsh_braking, harsh_acceleration, harsh_cornering, speeding или другой тип воздействия (potential_crash, crash, potential_fall, fall).
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕНормализованный 0.0–1.0 — насколько далеко за порог обнаружения прошло событие.
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕСкорость на мероприятии в м/с. Для удара скорость входа.
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕФизическая величина severity: g для суровых явлений и ударов, км/ч сверх допустимого для превышения скорости.
    latitude / longitudeНОТРАНС2ЛАТЕГде это произошло.
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕИСО-8601.
    НОТРАНС0ЛАТЕНОТРАНС1ЛАТЕСостояние строки на момент чтения — всегда false для события в проводе.

    speed и value присоединились к сохраняемой строке в 3.8.3. События, записанные в более старом отчете об установке 0 для обоих: столбцы не допускают значения NULL, поэтому обновленная база данных не может отличить старую строку от подлинного нуля.

  3. Группирование и повтор За один сброс происходит до 250 несинхронизированных событий, начиная с самых старых. Эта граница не зависит от maxBatchSize, который определяет размер только пакета местоположений.

    События помечаются как синхронизированные только в том случае, если запрос, который их перенес, удался. Неудачный POST — offline, 401, 503 — оставляет их в очереди для следующей попытки, а не удаляет их, точно так же, как и местоположения.

    Обратите внимание, что они отмечены, а не удалены: загруженное событие остается видимым для Tracelet.getTelematicsEvents(), поэтому ваше приложение по-прежнему может показывать драйверу свою собственную историю. Синхронизированный хвост обрезается до 1000 новейших строк, поэтому таблица не может расти вечно. Несинхронизированные строки никогда не обрезаются.

  4. Отдельная конечная точка (telematicsUrl) Если ваш бэкэнд маршрутизирует события движения куда-то кроме мест, установите telematicsUrl. Затем события передаются по собственному POST — телу {"telematics": [...]}, с теми же заголовками, таймаутами, повторными попытками и закреплением SSL, что и url — вместо того, чтобы использовать полезную нагрузку местоположения.

    http: tl.HttpConfig( url: 'https://api.example.com/locations', syncTelematics: true, telematicsUrl: 'https://api.example.com/telematics', ),

    Расход батареи меньше, чем кажется: оба запроса передаются один за другим внутри одного флеша, поэтому второй повторно использует уже активированное радио. Батарею разряжает второй загрузчик, работающий по собственному расписанию, а это не то. При очистке только для телематики (события в очереди, без новых местоположений) POST местоположения полностью пропускается, а не отправляется пустой.

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

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

    Tracelet.setSyncBodyBuilder((context) async { return { 'points': context.locations, 'events': context.telematics, // driving/impact events }; });

    context.telematics пуст, если syncTelematics не включен.

Поездки не сохраняются и не синхронизируются. Tracelet.onTrip() доставка завершенной поездки в обратный вызов Dart — это единственный способ, которым поездка покидает SDK: состояние поездки хранится в памяти, никогда не записывается в SQLite и никогда не отправляется на конечную точку HTTP. Если изолят с именем start() исчезнет до завершения отключения, событие отключения не будет создано вообще — оффлайн — не единственный способ его потерять. Пока не будет реализовано сохранение поездки, сохраните поездку самостоятельно в onTrip(), если она вам нужна, чтобы выжить. См. #356 .