You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React Native后台定位在三星Android 14设备上失效问题求助

Android 14三星设备React Native后台位置追踪失效解决方案

问题现象

我开发的React Native后台位置追踪代码在多数设备上运行正常,但搭载Android 14的三星设备出现异常:应用前台可正常获取位置,切换至后台后停止更新位置。该代码在Pixel 8等其他Android 14设备、Android版本低于14的三星设备上均正常。在BrowserStack测试时,三星Android 14设备会输出日志:E/Adreno-AppProfiles(20049): QSPM AIDL service doesn't exist。

问题根源

  • Android 14对前台服务的位置权限新增细分要求,普通FOREGROUND_SERVICE权限不足以支持后台位置追踪,需明确声明FOREGROUND_SERVICE_LOCATION权限。
  • 三星Android 14对后台进程限制更严格,getCurrentPosition单次位置请求易被系统拦截,watchPosition持续监听模式更稳定。
  • 日志中的Adreno相关报错属于三星GPU服务无关警告,不影响位置追踪,核心问题是权限与系统限制。

解决方案

1. 更新AndroidManifest权限配置

Android 14要求前台服务使用位置时,必须声明FOREGROUND_SERVICE_LOCATION权限,替换原有的FOREGROUND_SERVICE,同时调整权限声明顺序:

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />
    <!-- 替换为位置类型的前台服务权限 -->
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
    <uses-permission android:name="android.permission.WAKE_LOCK" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    <!-- 后台位置权限需在前台权限之后声明 -->
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>

    <application
        android:name=".MainApplication"
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:allowBackup="false"
        android:theme="@style/AppTheme">
        <activity
            android:name=".MainActivity"
            android:label="@string/app_name"
            android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
            android:launchMode="singleTask"
            android:windowSoftInputMode="adjustResize"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

2. 改用watchPosition实现持续位置追踪

将Geolocation.getCurrentPosition替换为Geolocation.watchPosition,持续监听模式更适配后台场景,同时管理监听ID避免内存泄漏:

import React, { useState } from 'react';
import {
    SafeAreaView,
    Text,
    StyleSheet,
    TouchableOpacity,
    PermissionsAndroid,
    Platform,
    Alert,
} from 'react-native';
import BackgroundService from 'react-native-background-actions';
import Geolocation from 'react-native-geolocation-service';

export default function App() {
    const [location, setLocation] = useState({});
    let watchId = null; // 保存位置监听ID

    const sleep = (time) => new Promise((resolve) => setTimeout(resolve, time));

    const requestLocationPermission = async () => {
        if (Platform.OS === 'android') {
            try {
                const fineLocationGranted = await PermissionsAndroid.request(
                    PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION,
                    {
                        title: 'Fine Location Permission',
                        message: 'This app needs access to your precise location.',
                        buttonNeutral: 'Ask Me Later',
                        buttonNegative: 'Cancel',
                        buttonPositive: 'OK',
                    }
                );

                if (fineLocationGranted !== PermissionsAndroid.RESULTS.GRANTED) {
                    Alert.alert('Permission Denied', 'Fine location permission is required.');
                    return false;
                }

                const coarseLocationGranted = await PermissionsAndroid.request(
                    PermissionsAndroid.PERMISSIONS.ACCESS_COARSE_LOCATION,
                    {
                        title: 'Coarse Location Permission',
                        message: 'This app needs access to your approximate location.',
                        buttonNeutral: 'Ask Me Later',
                        buttonNegative: 'Cancel',
                        buttonPositive: 'OK',
                    }
                );

                if (coarseLocationGranted !== PermissionsAndroid.RESULTS.GRANTED) {
                    Alert.alert('Permission Denied', 'Coarse location permission is required.');
                    return false;
                }

                const backgroundLocationGranted = await PermissionsAndroid.request(
                    PermissionsAndroid.PERMISSIONS.ACCESS_BACKGROUND_LOCATION,
                    {
                        title: 'Background Location Permission',
                        message: 'This app needs access to your location in the background.',
                        buttonNeutral: 'Ask Me Later',
                        buttonNegative: 'Cancel',
                        buttonPositive: 'OK',
                    }
                );

                if (backgroundLocationGranted !== PermissionsAndroid.RESULTS.GRANTED) {
                    Alert.alert('Permission Denied', 'Background location permission is required.');
                    return false;
                }

                return true;

            } catch (err) {
                console.warn(err);
                return false;
            }
        }
        return true;
    };

    const sendLocationToServer = async (location) => {
        try {
            console.log('Sending location to server:', location);
            const response = await fetch('https://dummy.com/location', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify(location),
            });
            const data = await response.text();
            console.log('Server response:', data);
        } catch (error) {
            console.error('Error sending location to server:', error);
        }
    };

    const veryIntensiveTask = async (taskDataArguments) => {
        const { delay } = taskDataArguments;

        await new Promise(async (resolve) => {
            // 启动持续位置监听
            watchId = Geolocation.watchPosition(
                async (position) => {
                    console.log('Location received:', position);
                    const newLocation = {
                        latitude: position.coords.latitude,
                        longitude: position.coords.longitude,
                        timestamp: new Date().toLocaleTimeString(),
                    };
                    setLocation(newLocation);
                    await sendLocationToServer(newLocation);
                    console.log('Location dispatched to server:', newLocation);
                },
                (error) => {
                    console.error('Error receiving location:', error);
                    // 位置获取失败时可添加重试逻辑
                },
                {
                    enableHighAccuracy: true,
                    distanceFilter: 10, // 适当调整距离过滤,减少无效请求
                    interval: 30000,
                    fastestInterval: 15000,
                    forceRequestLocation: true,
                    showLocationDialog: false, // 后台无需弹出位置请求对话框
                }
            );

            for (let i = 0; BackgroundService.isRunning(); i++) {
                await BackgroundService.updateNotification({
                    taskDesc: `Location tracking ${i}`,
                });
                await sleep(delay);
            }

            // 停止服务前移除位置监听
            if (watchId !== null) {
                Geolocation.clearWatch(watchId);
                watchId = null;
            }
            resolve();
        });
    };

    const options = {
        taskName: 'Location',
        taskTitle: 'Tracking Location',
        taskDesc: 'Tracking your location in the background',
        taskIcon: {
            name: 'ic_launcher',
            type: 'mipmap',
        },
        color: '#ff00ff',
        linkingURI: 'yourapp://location',
        parameters: {
            delay: 30000,
        },
        foreground: true,
    };

    const startBackgroundService = async () => {
        const hasLocationPermission = await requestLocationPermission();
        if (!hasLocationPermission) {
            return;
        }

        if (Platform.OS === 'android') {
            try {
                const isIgnoringOptimization = await BackgroundService.isIgnoringBatteryOptimizations();
                if (!isIgnoringOptimization) {
                    await BackgroundService.requestIgnoreBatteryOptimizations();
                }
            } catch (err) {
                console.warn(err);
            }
        }

        await BackgroundService.start(veryIntensiveTask, options);
        await BackgroundService.updateNotification({ taskDesc: 'Location tracking started' });
        console.log('Background service started');
    };

    const stopBackgroundService = async () => {
        // 提前移除位置监听
        if (watchId !== null) {
            Geolocation.clearWatch(watchId);
            watchId = null;
        }
        await BackgroundService.stop();
        console.log('Background service stopped');
    };

    return (
        <SafeAreaView style={styles.container}>
            <Text style={styles.text}>Location Tracker</Text>
            <TouchableOpacity style={styles.button} onPress={startBackgroundService}>
                <Text style={styles.buttonText}>Start Background Service</Text>
            </TouchableOpacity>
            <TouchableOpacity style={styles.button} onPress={stopBackgroundService}>
                <Text style={styles.buttonText}>Stop Background Service</Text>
            </TouchableOpacity>
            {location.latitude && (
                <Text style={styles.locationText}>
                    Latitude: {location.latitude}, Longitude: {location.longitude}, Time: {location.timestamp}
                </Text>
            )}
        </SafeAreaView>
    );
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#282C34',
        padding: 20,
    },
    text: {
        fontSize: 28,
        color: '#61DAFB',
        fontWeight: 'bold',
        marginBottom: 20,
    },
    button: {
        marginTop: 20,
        paddingVertical: 12,
        paddingHorizontal: 32,
        backgroundColor: '#61DAFB',
        borderRadius: 8,
        shadowColor: '#000',
        shadowOffset: { width: 0, height: 2 },
        shadowOpacity: 0.8,
        shadowRadius: 2,
        elevation: 5,
    },
    buttonText: {
        color: '#282C34',
        fontSize: 18,
        fontWeight: '600',
    },
    locationText: {
        fontSize: 18,
        color: 'white',
        marginTop: 20,
    },
});

3. 额外优化建议

  • 引导用户手动关闭三星设备后台限制:路径为「设置 → 电池和设备维护 → 电池 → 后台使用限制 → 找到应用 → 允许后台活动」
  • 使用物理设备测试,避免BrowserStack虚拟环境差异
  • 确保react-native-background-actions和react-native-geolocation-service为最新版本,修复已知兼容性问题

内容的提问来源于stack exchange,提问作者B1 FOOTBALL

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.23 14:52:03