Anonymous
Экспо отслеживание на переднем плане останавливается на Android при выключении экрана - отслеживание тропы неожиданно ос
Сообщение
Anonymous » 14 июн 2025, 12:00
Я использую Expo + React Native с помощью Expo-Location и Expo-Task-Manager, чтобы реализовать систему отслеживания местоположения переднего плана для отслеживания трасс для хлебной крошки, в то время как пользователь перспектирует в поле. Однако, когда экран выключен или приложение на некоторое время находится в фоновом режиме, отслеживание неожиданно останавливается, и когда пользователь возвращается в приложение, трасса для хлебной крошки больше не записывается.
Код: Выделить всё
import { useEffect, useRef, useState } from 'react';
import { showNativeToast } from '@/utils/useNativeToast';
import { useMapContext } from '@/context/MapContext';
import useToast from '@/hooks/useToast';
import { Platform } from 'react-native';
import * as Location from 'expo-location';
import * as Notifications from 'expo-notifications';
import { BreadcrumbFeature } from '@/types/breadcrumbTypes';
import { TRAIL_COLORS } from '@/constants/colors';
import * as TaskManager from 'expo-task-manager';
import { checkBatteryOptimization } from '@/utils/batteryOptimization';
import AsyncStorage from '@react-native-async-storage/async-storage';
import { ActivityType } from 'expo-location';
import { StorageKeys } from '@/constants/storageKeys';
import { calculateDistance } from '@/utils/calculateDistance';
const LOCATION_TASK_NAME = 'TrailTracking';
export const MIN_DISTANCE_METERS = 0;
const MAX_ACCURACY_METERS = 30;
const MAX_DISTANCE_JUMP_METERS = 50;
interface ReturnUseBreadcrumbTrail {
startTracking: () => Promise;
stopTracking: () => Promise;
discardPath: () => void;
setTrailName: (name: string) => void;
setTrailColor: (color: string) => void;
trailProperties: { name: string; color?: string };
addLocationPoint: (
coords:
| {
latitude: number;
longitude: number;
accuracy?: number;
speed?: number;
}
| {
latitude: number;
longitude: number;
accuracy?: number;
speed?: number;
}[],
) => void;
getCurrentBreadcrumb: () => BreadcrumbFeature;
}
const foregroundService = {
notificationTitle: 'Trail Tracking',
notificationBody: 'Your location is being tracked.',
channelId: 'location-tracking',
};
const locationConfig = {
accuracy: Location.Accuracy.BestForNavigation,
timeInterval: 0,
distanceInterval: 0,
foregroundService: Platform.OS === 'android' ? foregroundService : undefined,
pausesUpdatesAutomatically: false,
activityType: ActivityType.OtherNavigation,
mayRequireBackgroundAccess: true,
...(Platform.OS === 'ios' && {
allowsBackgroundLocationUpdates: true,
showsBackgroundLocationIndicator: true,
}),
};
export function useBreadcrumbTrail(): ReturnUseBreadcrumbTrail {
const { setIsBreadcrumbTracking, setLiveBreadcrumb, liveBreadcrumb } =
useMapContext();
const { showSuccessToast, showErrorToast } = useToast();
const [trailProperties, setTrailProperties] = useState({
name: '',
color: TRAIL_COLORS[0].color,
});
const isTracking = useRef(false);
const addLocationPoint = async (
coords:
| {
latitude: number;
longitude: number;
accuracy?: number;
speed?: number;
}
| {
latitude: number;
longitude: number;
accuracy?: number;
speed?: number;
}[],
) => {
setLiveBreadcrumb(prev => {
const coordinates = [...prev.geometry.coordinates];
const points = Array.isArray(coords) ? coords : [coords];
points.forEach(p => {
const isDriving = p.speed !== undefined && p.speed > 3.6; // ~10km/h
if (!isDriving && p.accuracy !== undefined && p.accuracy > MAX_ACCURACY_METERS) {
return;
}
const lastCoord =
coordinates.length > 0 ? coordinates[coordinates.length - 1] : null;
if (!lastCoord) {
coordinates.push([p.longitude, p.latitude]);
return;
}
const lastPoint = { latitude: lastCoord[1], longitude: lastCoord[0] };
const dist = calculateDistance(lastPoint, p);
if (isDriving || dist MIN_DISTANCE_METERS) {
coordinates.push([p.longitude, p.latitude]);
}
}
});
const updatedBreadcrumb = {
...prev,
geometry: {
...prev.geometry,
coordinates,
},
};
AsyncStorage.setItem(
StorageKeys.CURRENT_BREADCRUMB,
JSON.stringify(updatedBreadcrumb),
);
return updatedBreadcrumb;
});
try {
await AsyncStorage.removeItem('breadcrumb_points');
} catch (e) {
console.error('Failed to clear persisted breadcrumb points:', e);
}
};
useEffect(() => {
(async () => {
try {
const stored = await AsyncStorage.getItem('breadcrumb_points');
if (stored) {
const points = JSON.parse(stored);
if (points && points.length > 0) {
await addLocationPoint(points);
}
}
} catch (e) {
console.error('Failed to load persisted breadcrumb points:', e);
}
})();
return () => {
global.addLocationPoint = null;
};
}, []);
const startTracking = async (): Promise => {
try {
const { status } = await Location.requestForegroundPermissionsAsync();
if (status !== 'granted') {
showErrorToast('Permission to access location was denied');
return false;
}
if (Platform.OS === 'android') {
const isOptimized = await checkBatteryOptimization();
if (!isOptimized) {
return false;
}
}
const { granted } = await Notifications.requestPermissionsAsync({
ios: {
allowAlert: true,
allowBadge: true,
allowSound: true,
},
});
if (!granted) {
showErrorToast('Permission to display notification was denied');
return false;
}
if (Platform.OS === 'ios') {
const backgroundStatus =
await Location.requestBackgroundPermissionsAsync();
if (backgroundStatus.status !== 'granted') {
showErrorToast('Background location permission is required');
return false;
}
}
const isTaskRegistered =
await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME);
if (isTaskRegistered) {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
}
global.addLocationPoint = addLocationPoint;
await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, {
...locationConfig,
...(Platform.OS === 'android' && { foregroundService }),
});
isTracking.current = true;
setIsBreadcrumbTracking(true);
showSuccessToast('Tracking started');
return true;
} catch (error) {
console.warn('Error starting tracking:', error);
showNativeToast(
'Failed to start tracking. Please ensure location services are enabled and permissions are granted.',
);
return false;
}
};
const setTrailName = (name: string) => {
setTrailProperties(prev => ({ ...prev, name }));
setLiveBreadcrumb(prev => ({
...prev,
properties: { ...prev.properties, name },
}));
};
const setTrailColor = (color: string) => {
setTrailProperties(prev => ({ ...prev, color }));
setLiveBreadcrumb(prev => ({
...prev,
properties: { ...prev.properties, color },
}));
};
const stopTracking = async () => {
try {
const isTaskRegistered =
await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME);
if (isTaskRegistered) {
await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME);
}
isTracking.current = false;
setIsBreadcrumbTracking(false);
global.addLocationPoint = null;
} catch (error) {
console.warn('Error stopping tracking:', error);
isTracking.current = false;
setIsBreadcrumbTracking(false);
global.addLocationPoint = null;
}
};
const discardPath = () => {
if (isTracking.current) {
stopTracking();
}
setLiveBreadcrumb({
type: 'Feature',
geometry: {
type: 'LineString',
coordinates: [],
},
properties: {
name: '',
color: TRAIL_COLORS[0].color,
createdAt: Date.now(),
},
});
setTrailProperties({ name: '', color: TRAIL_COLORS[0].color });
};
const getCurrentBreadcrumb = (): BreadcrumbFeature => {
return {
...liveBreadcrumb,
properties: {
...liveBreadcrumb.properties,
color: trailProperties.color || TRAIL_COLORS[0].color,
createdAt: liveBreadcrumb.properties.createdAt || Date.now(),
},
};
};
useEffect(() => {
return () => {
if (isTracking.current) {
stopTracking();
}
};
}, []);
return {
startTracking,
stopTracking,
discardPath,
setTrailName,
setTrailColor,
trailProperties,
addLocationPoint,
getCurrentBreadcrumb,
};
}
Запрашивая разрешения на местоположение фонового местоположения (на iOS), но с использованием только отслеживания местоположения на переднем плане
предоставляется уведомления
Оптимизация батареи проверяется, и пользователи предлагаются, чтобы отключить его
Оптимирование, если
Определение. /> global.addlocationPoint () вызывается из defanetask (), чтобы добавить новые точки < /p>
Проблема < /strong>
на Android, если экран отключен или приложение на фона на некоторое время, отслеживание иногда молча останавливается < /p>
icon следом /> Похоже, что приложение, возможно, было убито или перезапущено, но нет никакого журнала аварии < /p>
ios работает, как и ожидалось - нет проблем < /p>
Подробнее здесь:
https://stackoverflow.com/questions/796 ... f-trail-tr
1749891612
Anonymous
Я использую Expo + React Native с помощью Expo-Location и Expo-Task-Manager, чтобы реализовать систему отслеживания местоположения переднего плана для отслеживания трасс для хлебной крошки, в то время как пользователь перспектирует в поле. Однако, когда экран выключен или приложение на некоторое время находится в фоновом режиме, отслеживание неожиданно останавливается, и когда пользователь возвращается в приложение, трасса для хлебной крошки больше не записывается.[code]import { useEffect, useRef, useState } from 'react'; import { showNativeToast } from '@/utils/useNativeToast'; import { useMapContext } from '@/context/MapContext'; import useToast from '@/hooks/useToast'; import { Platform } from 'react-native'; import * as Location from 'expo-location'; import * as Notifications from 'expo-notifications'; import { BreadcrumbFeature } from '@/types/breadcrumbTypes'; import { TRAIL_COLORS } from '@/constants/colors'; import * as TaskManager from 'expo-task-manager'; import { checkBatteryOptimization } from '@/utils/batteryOptimization'; import AsyncStorage from '@react-native-async-storage/async-storage'; import { ActivityType } from 'expo-location'; import { StorageKeys } from '@/constants/storageKeys'; import { calculateDistance } from '@/utils/calculateDistance'; const LOCATION_TASK_NAME = 'TrailTracking'; export const MIN_DISTANCE_METERS = 0; const MAX_ACCURACY_METERS = 30; const MAX_DISTANCE_JUMP_METERS = 50; interface ReturnUseBreadcrumbTrail { startTracking: () => Promise; stopTracking: () => Promise; discardPath: () => void; setTrailName: (name: string) => void; setTrailColor: (color: string) => void; trailProperties: { name: string; color?: string }; addLocationPoint: ( coords: | { latitude: number; longitude: number; accuracy?: number; speed?: number; } | { latitude: number; longitude: number; accuracy?: number; speed?: number; }[], ) => void; getCurrentBreadcrumb: () => BreadcrumbFeature; } const foregroundService = { notificationTitle: 'Trail Tracking', notificationBody: 'Your location is being tracked.', channelId: 'location-tracking', }; const locationConfig = { accuracy: Location.Accuracy.BestForNavigation, timeInterval: 0, distanceInterval: 0, foregroundService: Platform.OS === 'android' ? foregroundService : undefined, pausesUpdatesAutomatically: false, activityType: ActivityType.OtherNavigation, mayRequireBackgroundAccess: true, ...(Platform.OS === 'ios' && { allowsBackgroundLocationUpdates: true, showsBackgroundLocationIndicator: true, }), }; export function useBreadcrumbTrail(): ReturnUseBreadcrumbTrail { const { setIsBreadcrumbTracking, setLiveBreadcrumb, liveBreadcrumb } = useMapContext(); const { showSuccessToast, showErrorToast } = useToast(); const [trailProperties, setTrailProperties] = useState({ name: '', color: TRAIL_COLORS[0].color, }); const isTracking = useRef(false); const addLocationPoint = async ( coords: | { latitude: number; longitude: number; accuracy?: number; speed?: number; } | { latitude: number; longitude: number; accuracy?: number; speed?: number; }[], ) => { setLiveBreadcrumb(prev => { const coordinates = [...prev.geometry.coordinates]; const points = Array.isArray(coords) ? coords : [coords]; points.forEach(p => { const isDriving = p.speed !== undefined && p.speed > 3.6; // ~10km/h if (!isDriving && p.accuracy !== undefined && p.accuracy > MAX_ACCURACY_METERS) { return; } const lastCoord = coordinates.length > 0 ? coordinates[coordinates.length - 1] : null; if (!lastCoord) { coordinates.push([p.longitude, p.latitude]); return; } const lastPoint = { latitude: lastCoord[1], longitude: lastCoord[0] }; const dist = calculateDistance(lastPoint, p); if (isDriving || dist MIN_DISTANCE_METERS) { coordinates.push([p.longitude, p.latitude]); } } }); const updatedBreadcrumb = { ...prev, geometry: { ...prev.geometry, coordinates, }, }; AsyncStorage.setItem( StorageKeys.CURRENT_BREADCRUMB, JSON.stringify(updatedBreadcrumb), ); return updatedBreadcrumb; }); try { await AsyncStorage.removeItem('breadcrumb_points'); } catch (e) { console.error('Failed to clear persisted breadcrumb points:', e); } }; useEffect(() => { (async () => { try { const stored = await AsyncStorage.getItem('breadcrumb_points'); if (stored) { const points = JSON.parse(stored); if (points && points.length > 0) { await addLocationPoint(points); } } } catch (e) { console.error('Failed to load persisted breadcrumb points:', e); } })(); return () => { global.addLocationPoint = null; }; }, []); const startTracking = async (): Promise => { try { const { status } = await Location.requestForegroundPermissionsAsync(); if (status !== 'granted') { showErrorToast('Permission to access location was denied'); return false; } if (Platform.OS === 'android') { const isOptimized = await checkBatteryOptimization(); if (!isOptimized) { return false; } } const { granted } = await Notifications.requestPermissionsAsync({ ios: { allowAlert: true, allowBadge: true, allowSound: true, }, }); if (!granted) { showErrorToast('Permission to display notification was denied'); return false; } if (Platform.OS === 'ios') { const backgroundStatus = await Location.requestBackgroundPermissionsAsync(); if (backgroundStatus.status !== 'granted') { showErrorToast('Background location permission is required'); return false; } } const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME); if (isTaskRegistered) { await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); } global.addLocationPoint = addLocationPoint; await Location.startLocationUpdatesAsync(LOCATION_TASK_NAME, { ...locationConfig, ...(Platform.OS === 'android' && { foregroundService }), }); isTracking.current = true; setIsBreadcrumbTracking(true); showSuccessToast('Tracking started'); return true; } catch (error) { console.warn('Error starting tracking:', error); showNativeToast( 'Failed to start tracking. Please ensure location services are enabled and permissions are granted.', ); return false; } }; const setTrailName = (name: string) => { setTrailProperties(prev => ({ ...prev, name })); setLiveBreadcrumb(prev => ({ ...prev, properties: { ...prev.properties, name }, })); }; const setTrailColor = (color: string) => { setTrailProperties(prev => ({ ...prev, color })); setLiveBreadcrumb(prev => ({ ...prev, properties: { ...prev.properties, color }, })); }; const stopTracking = async () => { try { const isTaskRegistered = await TaskManager.isTaskRegisteredAsync(LOCATION_TASK_NAME); if (isTaskRegistered) { await Location.stopLocationUpdatesAsync(LOCATION_TASK_NAME); } isTracking.current = false; setIsBreadcrumbTracking(false); global.addLocationPoint = null; } catch (error) { console.warn('Error stopping tracking:', error); isTracking.current = false; setIsBreadcrumbTracking(false); global.addLocationPoint = null; } }; const discardPath = () => { if (isTracking.current) { stopTracking(); } setLiveBreadcrumb({ type: 'Feature', geometry: { type: 'LineString', coordinates: [], }, properties: { name: '', color: TRAIL_COLORS[0].color, createdAt: Date.now(), }, }); setTrailProperties({ name: '', color: TRAIL_COLORS[0].color }); }; const getCurrentBreadcrumb = (): BreadcrumbFeature => { return { ...liveBreadcrumb, properties: { ...liveBreadcrumb.properties, color: trailProperties.color || TRAIL_COLORS[0].color, createdAt: liveBreadcrumb.properties.createdAt || Date.now(), }, }; }; useEffect(() => { return () => { if (isTracking.current) { stopTracking(); } }; }, []); return { startTracking, stopTracking, discardPath, setTrailName, setTrailColor, trailProperties, addLocationPoint, getCurrentBreadcrumb, }; } [/code] Запрашивая разрешения на местоположение фонового местоположения (на iOS), но с использованием только отслеживания местоположения на переднем плане предоставляется уведомления Оптимизация батареи проверяется, и пользователи предлагаются, чтобы отключить его Оптимирование, если Определение. /> global.addlocationPoint () вызывается из defanetask (), чтобы добавить новые точки < /p> Проблема < /strong> на Android, если экран отключен или приложение на фона на некоторое время, отслеживание иногда молча останавливается < /p> icon следом /> Похоже, что приложение, возможно, было убито или перезапущено, но нет никакого журнала аварии < /p> ios работает, как и ожидалось - нет проблем < /p> Подробнее здесь: [url]https://stackoverflow.com/questions/79665708/expo-foreground-location-tracking-stops-on-android-when-screen-is-off-trail-tr[/url]