You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

LocationManager问题:设备进入休眠模式时无法获取更新位置

解决Android LocationManager休眠时无法获取新位置的问题

这个问题我之前帮不少开发者处理过,核心原因其实是Android系统的休眠省电机制:当设备屏幕关闭进入休眠状态后,系统会主动降低后台进程的资源优先级,甚至暂停GPS这类高耗电传感器的主动更新,这时候LocationManager返回的只是系统缓存的最后一次有效位置,而非实时的新位置。等设备重新活跃(屏幕点亮),传感器被重新激活,自然就能获取到新位置了。

下面给你两种可行的解决方案,优先推荐第一种,因为是Google官方优化过的方案:

方案一:使用FusedLocationProviderClient(推荐)

Google的融合位置API(Fused Location Provider)专门针对后台位置获取场景做了优化,能在平衡耗电和更新频率的前提下,让你的Service在设备休眠时也能持续获取位置更新(前提是正确配置权限和前台服务)。

步骤说明

  1. 添加依赖:在你的app模块的build.gradle文件中加入Play Services Location依赖:
implementation "com.google.android.gms:play-services-location:21.0.1"
  1. 申请必要权限
    • 前台位置追踪:需要ACCESS_FINE_LOCATIONACCESS_COARSE_LOCATION权限
    • 后台位置追踪:Android 10及以上必须额外申请ACCESS_BACKGROUND_LOCATION权限,记得在Manifest中声明,并且要动态向用户申请
  2. Service中实现位置更新
    创建LocationRequest配置更新参数,用FusedLocationProviderClient发起请求,同时要把Service设为前台服务(Android O及以上强制要求,否则后台进程会被系统杀死)。

代码示例(Kotlin)

class LocationTrackingService : Service() {
    private lateinit var fusedLocationClient: FusedLocationProviderClient
    private lateinit var locationCallback: LocationCallback

    override fun onCreate() {
        super.onCreate()
        fusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        
        // 定义位置更新回调
        locationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
                super.onLocationResult(locationResult)
                locationResult.lastLocation?.let { location ->
                    // 这里处理获取到的新位置
                    Log.d("LocationService", "实时位置:${location.latitude}, ${location.longitude}")
                }
            }
        }

        // 启动前台服务,避免被系统回收
        startForeground(1, createForegroundNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        startLocationUpdates()
        return START_STICKY // 服务被杀死后自动重启
    }

    private fun startLocationUpdates() {
        val locationRequest = LocationRequest.create().apply {
            interval = 10000 // 每10秒请求一次位置
            fastestInterval = 5000 // 最快5秒更新一次(如果有新位置的话)
            priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY // 平衡耗电与精度
        }

        // 检查权限是否已授予
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED &&
            ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION)
            == PackageManager.PERMISSION_GRANTED
        ) {
            fusedLocationClient.requestLocationUpdates(
                locationRequest,
                locationCallback,
                Looper.getMainLooper()
            )
        }
    }

    // 创建前台服务通知
    private fun createForegroundNotification(): Notification {
        val channelId = "LocationTrackingChannel"
        // Android O及以上需要创建通知渠道
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "位置追踪服务",
                NotificationManager.IMPORTANCE_LOW // 低优先级,避免打扰用户
            )
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }

        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("位置追踪中")
            .setContentText("正在获取你的实时位置")
            .setSmallIcon(R.drawable.ic_location)
            .build()
    }

    override fun onDestroy() {
        super.onDestroy()
        // 停止位置更新,避免内存泄漏
        fusedLocationClient.removeLocationUpdates(locationCallback)
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

方案二:坚持使用原生LocationManager

如果你因为某些原因必须使用原生的LocationManager,也可以通过以下方式解决,但注意这种方式耗电会更高:

关键调整点

  1. 使用PARTIAL_WAKE_LOCK:保持CPU处于唤醒状态,这样即使屏幕关闭,LocationManager也能继续获取位置更新
  2. 转为前台服务:同样要把Service设为前台服务,避免被系统杀死
  3. 配置合理的更新参数:设置合适的时间间隔和距离阈值

代码示例(Kotlin)

class NativeLocationService : Service() {
    private lateinit var locationManager: LocationManager
    private var wakeLock: PowerManager.WakeLock? = null
    private val locationListener = object : LocationListener {
        override fun onLocationChanged(location: Location) {
            // 处理新位置
            Log.d("NativeLocation", "实时位置:${location.latitude}, ${location.longitude}")
        }

        @Deprecated("Deprecated in Java")
        override fun onStatusChanged(provider: String?, status: Int, extras: Bundle?) {}

        override fun onProviderEnabled(provider: String) {}

        override fun onProviderDisabled(provider: String) {}
    }

    override fun onCreate() {
        super.onCreate()
        locationManager = getSystemService(LOCATION_SERVICE) as LocationManager
        
        // 获取Partial WakeLock,保持CPU唤醒
        val powerManager = getSystemService(POWER_SERVICE) as PowerManager
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "NativeLocation:WakeLock")
        wakeLock?.acquire()

        // 启动前台服务
        startForeground(2, createForegroundNotification())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        requestLocationUpdates()
        return START_STICKY
    }

    private fun requestLocationUpdates() {
        if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
            == PackageManager.PERMISSION_GRANTED
        ) {
            // 使用GPS_PROVIDER需要用户开启GPS;如果不需要高精度,可改用NETWORK_PROVIDER
            locationManager.requestLocationUpdates(
                LocationManager.GPS_PROVIDER,
                10000, // 10秒间隔
                0f, // 距离变化为0时也更新(只按时间触发)
                locationListener,
                Looper.getMainLooper()
            )
        }
    }

    private fun createForegroundNotification(): Notification {
        val channelId = "NativeLocationChannel"
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val channel = NotificationChannel(
                channelId,
                "原生位置服务",
                NotificationManager.IMPORTANCE_LOW
            )
            getSystemService(NotificationManager::class.java).createNotificationChannel(channel)
        }

        return NotificationCompat.Builder(this, channelId)
            .setContentTitle("原生位置追踪中")
            .setContentText("正在获取你的实时位置")
            .setSmallIcon(R.drawable.ic_location)
            .build()
    }

    override fun onDestroy() {
        super.onDestroy()
        locationManager.removeUpdates(locationListener)
        wakeLock?.release() // 释放WakeLock,避免持续耗电
    }

    override fun onBind(intent: Intent?): IBinder? = null
}

重要注意事项

  • 权限问题:Android 10+必须申请ACCESS_BACKGROUND_LOCATION权限,否则后台无法获取位置;Android 12+还有更细粒度的权限控制,要根据你的需求正确申请
  • 耗电优化:WakeLock会显著增加设备耗电,所以非必要情况下优先用FusedLocationProvider,它的智能调度能更好地平衡耗电和位置更新需求
  • 前台服务:Android O及以上,后台服务无法长期运行,必须转为前台服务,否则会被系统强制停止,一定要记得创建通知并调用startForeground

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

火山引擎 最新活动