Android应用被杀死后WorkManager启动前台服务仍无法获取定位
问题根因与修复方案
1. 缺少后台定位必要权限
Android 10(API 29)及以上系统,应用退到后台后获取定位必须申请ACCESS_BACKGROUND_LOCATION权限,你当前的权限申请逻辑只申请了前台定位权限,应用被划走后属于后台场景,自然无法拿到定位结果。
- 修复步骤:
- 先在Manifest中添加权限声明:
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" /> <!-- Android 12 及以上还需要添加前台服务定位权限 --> <uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" /> - 修改权限申请逻辑,先申请前台定位权限,用户同意后再引导用户授予后台定位权限:
override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) when(requestCode) { 1 -> { // 前台定位权限申请结果 if (grantResults.all { it == PackageManager.PERMISSION_GRANTED }) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { // 申请后台定位权限 ActivityCompat.requestPermissions( this, arrayOf(android.Manifest.permission.ACCESS_BACKGROUND_LOCATION), 2 ) } else { // 低版本直接初始化WorkManager initLocationWorker() } } } 2 -> { // 后台定位权限申请结果 if (grantResults.firstOrNull() == PackageManager.PERMISSION_GRANTED) { initLocationWorker() } } } } private fun initLocationWorker() { val request = PeriodicWorkRequestBuilder<GeoLocationWorker>(15, TimeUnit.MINUTES) .build() WorkManager .getInstance(this) .enqueueUniquePeriodicWork( WorkerConfig.LOCATION_WORKER, ExistingPeriodicWorkPolicy.KEEP, request ) finish() startActivity(Intent(this, HomeActivity::class.java)) }
- 先在Manifest中添加权限声明:
2. Worker执行逻辑错误
你在启动前台服务后直接返回Result.success(),WorkManager会认为任务已经执行完成,会降低应用进程优先级,甚至直接杀掉进程,此时定位请求还没返回结果,自然拿不到回调。
- 修复方案:将Worker改为
CoroutineWorker,等待定位结果返回后再结束Worker,或者用ListenableWorker的异步回调机制:class GeoLocationWorker(context: Context, workerParameters: WorkerParameters) : CoroutineWorker(context, workerParameters) { override suspend fun doWork(): Result { Log.i("GLW", "location lookup start") // 启动前台服务 applicationContext.startForegroundService(Intent(applicationContext, GeoService::class.java)) // 等待定位结果返回,最多等待30秒 var retryCount = 0 while (retryCount < 30) { delay(1000) // 可以通过SharedPreference或者广播接收定位结果的标记,确认定位完成后再返回 if (getLocationResultFlag()) { break } retryCount++ } return Result.success() } }
3. 定位Provider逻辑不完善
你直接固定使用GPS_PROVIDER,如果设备在室内没有GPS信号,会直接返回null。
- 修复方案:优先使用可用的Provider,或者同时请求多个Provider的定位结果,取可用的:
// 先判断可用的Provider val providers = locationManager.getProviders(true) val locationProvider = when { providers.contains(LocationManager.GPS_PROVIDER) -> LocationManager.GPS_PROVIDER providers.contains(LocationManager.NETWORK_PROVIDER) -> LocationManager.NETWORK_PROVIDER else -> { Log.e("location", "no available location provider") stopForeground(true) stopSelf() return } } // 增加超时机制,避免一直等待 val executor = Executors.newSingleThreadExecutor() locationManager.getCurrentLocation( locationProvider, null, executor, { location -> if (location == null) { // 尝试用getLastKnownLocation兜底 val lastKnown = locationManager.getLastKnownLocation(locationProvider) lastKnown?.let { Log.i("location", "last known location: $it") } ?: Log.e("location", "location == null") } else { Log.i("location", location.toString()) } // 保存定位结果标记,通知Worker任务完成 saveLocationResultFlag() stopForeground(true) stopSelf() }) // 10秒超时后直接结束 Handler(Looper.getMainLooper()).postDelayed({ executor.shutdownNow() stopForeground(true) stopSelf() }, 10000)
4. 服务逻辑优化
你当前的GeoService没有主动调用stopSelf(),即使定位完成后服务还会在后台运行,容易被系统杀掉,也不符合后台权限规范,定位完成后一定要主动调用stopSelf()销毁服务。
额外说明
如果是国内定制ROM,还需要引导用户开启应用的后台活动权限、自启动权限,关闭省电优化,否则系统会限制应用后台运行,导致定位失败。
内容的提问来源于stack exchange,提问作者jjakubowski
相关产品推荐
相关产品推荐

