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 решает эту проблему
-
Сохранение SQLite в автономном режиме (
autoSyncThreshold) Поскольку у Марка нет сигнала, Tracelet немедленно прекращает попытки отправлять HTTP-запросы. Вместо этого каждое местоположение надежно хранится в локальной базе данных SQLite. Мы установилиautoSyncThreshold: 100, что означает, что Tracelet даже не пытается разбудить сетевое радио, пока в базе данных не будет поставлено в очередь как минимум 100 точек. -
Устранение дребезга синхронизации (
autoSyncDelay) Когда Марк наконец съезжает с горы и восстанавливает 4G, у него внезапно оказывается 500 мест в очереди. Вместо того, чтобы немедленно отправить 500 HTTP-запросов (что привело бы к зависанию его телефона),autoSyncDelay: 10000сообщает Tracelet подождать 10 секунд. Он препятствует быстрому притоку данных, позволяя стабилизировать соединение. -
Пакетная синхронизация (
batchSyncиmaxBatchSize) Вместо 500 отдельных запросовPOSTbatchSync: trueиmaxBatchSize: 250объединяют местоположения в два массивных массива JSON. Он отправляет первые 250 точек, ждет, пока ваш сервер вернетHTTP 200 OK, удаляет эти точки из SQLite, а затем отправляет следующий пакет. -
Интервальная синхронизация (
syncInterval)autoSyncDelayреагирует на новые локации. Если вам также нужна очистка по времени — загрузка всего, что находится в очереди, с фиксированной частотой, независимо от того, сколько очков накоплено — установитеsyncIntervalна количество секунд между сбросами (например,syncInterval: 60очищает автономную очередь раз в минуту). Он работает параллельно с устранением дребезга и отключен по умолчанию (0).
Сценарий 2. Подключение к Wi-Fi в кафе.
Изученные концепции: Сотовые ограничения, дельта-сжатие.
Проблема
Марк заканчивает поход и идет в кафе. Он путешествует по всему миру, поэтому его тарифный план сотовой связи чрезвычайно дорог. Ваше приложение поставило в очередь мегабайты данных о местоположении в формате JSON, и отправка их через роуминговое соединение 4G будет стоить ему денег.
Как Tracelet Sync решает эту проблему
-
Ограничение сотовой связи (
disableAutoSyncOnCellular) УстановивdisableAutoSyncOnCellular: true, Tracelet полностью блокирует механизм синхронизации, пока Марк находится в сети 4G. Местоположение остается в безопасности в SQLite. В тот момент, когда он подключается к Wi-Fi кафе, операционная система пробуждает Tracelet, и механизм синхронизации автоматически очищает очередь. -
Сжатие дельта-кодирования (
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 решает эту проблему
- Обратные вызовы динамического заголовка Когда 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);- Экспоненциальный откат (
maxRetriesиretryBackoffCap) Что, если ваш сервер аутентификации не работает и возвращает 503? Tracelet прекрасно с этим справляется. Он пытается повторить попытку. Это терпит неудачу. Он ждет 1 секунду (retryBackoffBase), затем 2 секунды, затем 4 секунды. Экспоненциальная задержка ограничена 60 секундами (retryBackoffCap). После трех попыток (maxRetries) он полностью сдается, оставляя данные в безопасности в SQLite, чтобы повторить попытку завтра.
Сценарий 4. Схема пользовательского сервера
Изученные концепции: Custom Sync Body Builders, сопоставление схем.
Проблема
У компании Марка есть устаревшая серверная часть, которая принимает данные о местоположении в очень специфическом, нестандартном формате. Полезная нагрузка JSON Tracelet по умолчанию не соответствует требуемой схеме их сервера, и они не могут изменить внутренний API только для этого приложения.
Как Tracelet Sync решает эту проблему
- 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,
};
});- Безголовая казнь
Так же, как и обновление токенов, это создание пользовательского тела также может выполняться полностью в фоновом режиме через
registerHeadlessSyncBodyBuilder(), гарантируя, что ваша пользовательская схема будет построена и отправлена даже после полного закрытия приложения.
Сценарий 5: Водитель доставки
Изученные концепции: контекст маршрута и внедрение бизнес-логики
Проблема
Ваш сервер получает тысячи необработанных координат. Но одна только координата не скажет вам, почему пользователь был там. Был ли водитель занят доставкой? Какой заказ они доставили? Вам нужен способ прикрепить бизнес-логику непосредственно к полезной нагрузке фонового местоположения, чтобы вы могли легко запрашивать ее в своей базе данных.
Как Tracelet Sync решает эту проблему
-
Настройка контекста маршрута Вы можете внедрить собственные метаданные в Tracelet. Каждое местоположение, записанное после вызова
setRouteContext(), будет автоматически помечено этими данными во внутренней базе данных SQLite.await tl.Tracelet.setRouteContext( const tl.RouteContext( taskId: 'delivery-1234', driverId: 'john_doe', custom: {'shift_id': 'morning-shift-001'}, ), ); -
Результирующая полезная нагрузка 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.
- Когда устройство снова подключается к сети, механизм синхронизации передает точное историческое состояние батареи, а не текущее состояние батареи.
Это позволяет вашему серверу точно визуализировать разряд батареи по маршруту или определить, постоянно ли водители отключают свои устройства во время смен.
-
Очистка контекста Когда водитель закончит доставку, очистите контекст. Последующие местоположения больше не будут отмечены тегами.
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'")
);