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

Android Kotlin:OEM设备Doze模式下获取Location为null求助

问题:Doze模式下FusedLocationProviderClient无法获取位置(多OEM设备)
  • 使用FusedLocationProviderClient结合JobScheduler持续获取位置,后台运行正常,但进入Doze模式后getCurrentLocation()返回null,LocationCallback的onLocationResult()也不再触发
  • 问题出现在三星、小米、POCO、摩托罗拉、华为等OEM设备
  • 三星设备进入Doze模式时的日志:

    RequestManager_FLP com.sec.location.nsflp2 E [LocationManager] Location request 2C7EC95F(Listener) gps interval=0ms (min=0ms) from com.google.android.gms.persistent[WorkSource com....] (10200_FINE_fg_svc_true_foreground)

  • FakeGPS、Google Maps、Uber Driver、Life360等应用在Doze模式下可正常获取位置

相关代码

LocationJob.kt

class LocationJob : JobService() {

    private var mLocationRequest: LocationRequest? = null
    private var mLocationRequestBuilder: LocationRequest.Builder? = null
    private var mFusedLocationClient: FusedLocationProviderClient? = null
    private var mLocationCallback: LocationCallback? = null

    private val workHandler: Handler = Handler(Looper.getMainLooper())
    var workRunnable: Runnable? = null
    var handlerThread: HandlerThread? = null

    override fun onCreate() {
        super.onCreate()
        // TODO: Start a background thread to receive location result.
        handlerThread = HandlerThread("RequestLocation");
        handlerThread!!.start();

        mFusedLocationClient = LocationServices.getFusedLocationProviderClient(this)
        mLocationCallback = object : LocationCallback() {
            override fun onLocationResult(locationResult: LocationResult) {
               //Note: this is not called in case of Doze mode in OEM device.  
                if (locationResult.lastLocation == null) {
                    Log.e(TAG, "Location missing in callback.")
                } else {
                    Log.e(TAG, "Location callback. ${locationResult.lastLocation}")
                }
                super.onLocationResult(locationResult)

            }

            override fun onLocationAvailability(locationAvailability: LocationAvailability) {
                super.onLocationAvailability(locationAvailability)
            }
        }
        if (mLocationRequest == null) {
           if (mLocationRequestBuilder == null){
               mLocationRequestBuilder = LocationRequest.Builder(
                  Priority.PRIORITY_HIGH_ACCURACY,
                  sessionManager?.moveOnTripInterval.makeLong()
               )
            }
            mLocationRequestBuilder?.setIntervalMillis()
            mLocationRequestBuilder?.setMinUpdateIntervalMillis()
            mLocationRequest = mLocationRequestBuilder!!.build()
        }
    }

override fun onStartJob(params: JobParameters?): Boolean {
        Log.e(TAG, "onStartJob")
        if (mFusedLocationClient != null) return false

        workRunnable = Runnable {
            try {
                mFusedLocationClient!!.requestLocationUpdates(
                    mLocationRequest!!, mLocationCallback!!, handlerThread!!.looper
                )

                Handler(Looper.getMainLooper()).postDelayed({
                    mFusedLocationClient!!.getCurrentLocation(PRIORITY_HIGH_ACCURACY, null)
                        .addOnSuccessListener {
                            Log.e("Loc: $it")
                            if (it != null) {
                                val loc = it
                                println("$TAG: ${loc.latitude},${loc.longitude}")
                            } else {
                                Log.e("Loc: is null")
                            }
                        }.addOnFailureListener {
                            println("cancel")
                        }

                }, 10 * 1000)
            } catch (unlikely: SecurityException) {
                println("$TAG Lost location permission. Could not request updates. ")
            }
            jobFinished(params, true)
        }
        workHandler.post(workRunnable!!)
        return true
    }
}

MainActivity.kt

class MainActivity: AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      //trigger LocationJob service
        triggerJobService(true)
    }

    fun triggerJobService(start: Boolean) {
        try {
            val jobScheduler =
                getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
            if (start) {
                val jobInfo =
                    JobInfo.Builder(Constant.JOB_ID, ComponentName(this, LocationJob::class.java))
                        .setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY)
                        .setRequiresDeviceIdle(false).setRequiresCharging(false).setPersisted(true)
//                        .setOverrideDeadline(3 * 60 * 1000)
                val job = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
                    jobInfo.setMinimumLatency(Constant.REFRESH_INTERVAL)
                        .build()
                } else {
                    jobInfo.setPeriodic(Constant.REFRESH_INTERVAL)
                        .setMinimumLatency(1)
                        .build()
                }
                //.setOverrideDeadline(3 * 60 * 1000)
                jobScheduler.schedule(job)
            } else {
                jobScheduler.cancel(Constant.JOB_ID)
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return
        }
    }

}

解决方案

1. 替换JobScheduler为WorkManager

JobScheduler在Doze模式下会被系统严格限制,WorkManager是Google官方推荐的后台任务管理方案,针对位置等特殊场景做了适配,支持Android 12+的精确周期性任务,能更好地绕过Doze限制。

2. 完善权限与位置请求配置

  • 确保已申请ACCESS_BACKGROUND_LOCATION权限(Android 10+),并在运行时动态申请
  • 调整LocationRequest配置:
    mLocationRequestBuilder = LocationRequest.Builder(
        Priority.PRIORITY_HIGH_ACCURACY,
        sessionManager?.moveOnTripInterval.makeLong()
    )
    .setMinUpdateIntervalMillis(10000) // 设置最小更新间隔
    .setWaitForAccurateLocation(true) // 等待高精度位置(可选)
    

3. 改用前台服务绑定位置更新

Doze模式下后台进程优先级极低,前台服务拥有更高的系统优先级:

  • 创建前台服务,调用startForeground()显示必要通知(系统强制要求)
  • 在前台服务中发起requestLocationUpdates,保证位置回调能正常触发

4. 处理OEM厂商特殊限制

小米、华为、三星等厂商有自定义的后台管理机制,需引导用户手动配置:

  • 跳转系统设置页面,让用户将应用加入“后台白名单”或“忽略电池优化”
  • 使用Intent(ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS)快速跳转设置

5. 修正现有代码逻辑错误

  • onStartJob中if (mFusedLocationClient != null) return false逻辑错误,应改为if (mFusedLocationClient == null) return false,否则会直接终止任务
  • 调用requestLocationUpdates后立即执行jobFinished(params, true)会导致JobService被销毁,无法接收回调,需在onLocationResult中获取到位置后再结束任务
  • getCurrentLocation依赖系统缓存,Doze模式下缓存可能失效,优先依赖LocationCallback的实时更新

关键代码调整示例

修改onStartJob逻辑:

override fun onStartJob(params: JobParameters?): Boolean {
    Log.e(TAG, "onStartJob")
    if (mFusedLocationClient == null) return false

    workRunnable = Runnable {
        try {
            mFusedLocationClient!!.requestLocationUpdates(
                mLocationRequest!!, object : LocationCallback() {
                    override fun onLocationResult(locationResult: LocationResult) {
                        super.onLocationResult(locationResult)
                        val location = locationResult.lastLocation
                        if (location != null) {
                            Log.e(TAG, "Location received: ${location.latitude},${location.longitude}")
                            // 处理位置数据
                        } else {
                            Log.e(TAG, "Location missing")
                        }
                        // 获取位置后停止更新并结束任务
                        mFusedLocationClient!!.removeLocationUpdates(this)
                        jobFinished(params, true)
                    }
                }, handlerThread!!.looper
            )
        } catch (unlikely: SecurityException) {
            println("$TAG Lost location permission. Could not request updates. ")
            jobFinished(params, false)
        }
    }
    workHandler.post(workRunnable!!)
    return true
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 09:48:10