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

Android 14中每2小时触发的PendingIntent未生效排查求助

问题描述

我有个疑问:每次通过BroadcastReceiver创建OneTimeWorker时,能不能在Worker内部获取当前位置?

我的实现流程:Fragment按钮触发PendingIntent到BroadcastReceiver,Receiver代码如下:

class AlarmManagerReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val constraints = Constraints.Builder()
            .setRequiredNetworkType(NetworkType.CONNECTED)
            .build()

        val workRequest = OneTimeWorkRequestBuilder<WeatherWorker>()
            .addTag("OneTimeJobNew")
            .setConstraints(constraints)
            .build()

        WorkManager.getInstance(context!!).enqueue(workRequest)
    }
}

WeatherWorker类代码:

@HiltWorker
class WeatherWorker @AssistedInject constructor(
    @Assisted appContext: Context,
    @Assisted workerParams: WorkerParameters,
    private val repository: WeatherRepository,
    private val fusedLocationClient: FusedLocationProviderClient
) : CoroutineWorker(appContext, workerParams) {

    override suspend fun doWork(): Result {
        return try {
            // 检查后台位置权限
            if (ActivityCompat.checkSelfPermission(
                    applicationContext,
                    Manifest.permission.ACCESS_BACKGROUND_LOCATION
                ) == PackageManager.PERMISSION_GRANTED
            ) {
                var nextDayWeather :Any = Any()
                // 获取位置
                val locationResult = fusedLocationClient.lastLocation
                val taskResult = Tasks.await(locationResult)
                val lat = taskResult.latitude
                val long = taskResult.longitude
                val result = repository.fetchWeather("$lat,$long")
               
                if (result.status is Success) {
                    val currentDate = Calendar.getInstance()
                    currentDate.add(Calendar.DAY_OF_YEAR, 1)
                    val nextDate = currentDate.time

                    val dateFormat = SimpleDateFormat("yyyy-MM-dd")
                    var nextDateStr = dateFormat.format(nextDate)
                    val forecastDayResult = result.status.data!!.forecast.forecastday
                    for (dayWeek in forecastDayResult){
                        if(dayWeek.date.contains(nextDateStr)){
                            nextDayWeather = dayWeek
                            break
                        }
                    }

                    if(nextDayWeather is forecastday){
                        AppUtils.notify(applicationContext,"Tomorrow's Weather","Weather Status: " + "${nextDayWeather.day.condition.text} " +
                                " Average Temperature: " + "${nextDayWeather.day.avgtemp_c.toInt()}",
                            R.mipmap.ic_launcher_weather_round)
                    }

                    // 调度2小时后的闹钟
                    val intent = Intent(applicationContext, AlarmManagerReceiver::class.java)
                    val pendingIntent = PendingIntent.getBroadcast(
                        applicationContext,
                        1,
                        intent,
                        PendingIntent.FLAG_IMMUTABLE
                    )
                    val alarmManager =
                        applicationContext.getSystemService(Context.ALARM_SERVICE) as AlarmManager

                    val calendar = Calendar.getInstance()
                    calendar.timeInMillis = System.currentTimeMillis()
                    calendar.add(Calendar.HOUR, 2)

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        val canScheduleExactAlarms = alarmManager.canScheduleExactAlarms()
                        if (!canScheduleExactAlarms) {
                            // 未处理权限拒绝情况
                        }
                    }

                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                        alarmManager.setExactAndAllowWhileIdle(
                            AlarmManager.RTC_WAKEUP,
                            calendar.timeInMillis,
                            pendingIntent
                        )
                    } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                        alarmManager.setExact(
                            AlarmManager.RTC_WAKEUP,
                            calendar.timeInMillis,
                            pendingIntent
                        )
                    } else {
                        alarmManager.set(
                            AlarmManager.RTC_WAKEUP,
                            calendar.timeInMillis,
                            pendingIntent
                        )
                    }
                    Result.success()
                } else {
                    Result.failure()
                }
            } else {
                AppUtils.notify(applicationContext,"Error","Please select allow all the time from settings.")
                Result.failure()
            }
        }
        catch (e: Exception) {
            Result.failure()
        }
    }
}

Manifest权限配置:

<uses-permission android:name="android.permission.INTERNET"/>

<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<!-- or -->
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />

<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />

现在遇到的问题:设置的2小时后触发的PendingIntent完全没生效,针对Android 14版本找不到解决方案。我知道后台位置请求有次数限制,但我是每2小时触发一次,请问可能的错误原因是什么?


问题分析与解决方案

针对你在Android 14上面临的2小时后PendingIntent不触发的问题,以下是几个核心原因及对应的解决思路:

1. 精确闹钟权限未主动申请

Android 12及以上版本,SCHEDULE_EXACT_ALARM权限默认拒绝,即使Manifest声明了权限,也必须主动引导用户到系统设置开启。你当前代码仅判断权限状态但未处理拒绝场景,导致闹钟无法被调度。

解决方法:
当检测到权限未授权时,跳转至系统设置页面:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    val canScheduleExactAlarms = alarmManager.canScheduleExactAlarms()
    if (!canScheduleExactAlarms) {
        val intent = Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        applicationContext.startActivity(intent)
        AppUtils.notify(applicationContext,"权限提示","需要开启精确闹钟权限以继续获取天气")
    }
}

2. PendingIntent的requestCode重复

你使用固定的requestCode = 1创建PendingIntent,每次调度新闹钟时会覆盖旧的PendingIntent。若旧闹钟未触发就被覆盖,会导致调度失效。

解决方法:
使用唯一的requestCode,比如基于时间戳:

val requestCode = System.currentTimeMillis().toInt()
val pendingIntent = PendingIntent.getBroadcast(
    applicationContext,
    requestCode,
    intent,
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

3. Android 14后台任务限制触发

Android 14对后台空闲应用的任务调度限制更严格,若应用长时间未被用户打开,系统可能推迟或取消闹钟调度。手动维护AlarmManager循环的方式可靠性远低于WorkManager的周期性任务。

解决方法:
改用WorkManager的PeriodicWorkRequest替代手动循环:

// 在BroadcastReceiver中直接调度周期性任务
val periodicRequest = PeriodicWorkRequestBuilder<WeatherWorker>(2, TimeUnit.HOURS)
    .setConstraints(constraints)
    .build()
WorkManager.getInstance(context!!).enqueueUniquePeriodicWork(
    "WeatherUpdate",
    ExistingPeriodicWorkPolicy.REPLACE,
    periodicRequest
)

WorkManager会自动适配系统的后台限制,无需手动维护AlarmManager。

4. 位置获取失败导致Worker提前终止

若fusedLocationClient.lastLocation返回null,Tasks.await(locationResult)会抛出异常,导致Worker直接返回Result.failure(),后续的闹钟调度代码根本不会执行。

解决方法:
增加位置获取的容错逻辑,当lastLocation为空时请求单次位置更新,并改为挂起方式处理:

suspend fun getCurrentLocation(): Location? {
    return try {
        fusedLocationClient.lastLocation.await() ?: run {
            val locationDeferred = CompletableDeferred<Location?>()
            val callback = object : LocationCallback() {
                override fun onLocationResult(result: LocationResult) {
                    super.onLocationResult(result)
                    fusedLocationClient.removeLocationUpdates(this)
                    locationDeferred.complete(result.lastLocation)
                }
            }
            fusedLocationClient.requestLocationUpdates(
                LocationRequest.create().apply {
                    priority = LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY
                    numUpdates = 1
                },
                callback,
                Looper.getMainLooper()
            )
            locationDeferred.await()
        }
    } catch (e: Exception) {
        null
    }
}

// 在doWork中调用
val location = getCurrentLocation() ?: run {
    AppUtils.notify(applicationContext,"错误","无法获取当前位置")
    return Result.retry() // 重试而非直接失败
}
val lat = location.latitude
val long = location.longitude

5. 位置权限未正确授权

Android 14中ACCESS_BACKGROUND_LOCATION需要用户主动选择"始终允许",若仅授予"仅在使用应用时允许",位置获取逻辑会失败,导致Worker返回失败,后续闹钟调度无法执行。

解决方法:
在Fragment中请求位置权限时,明确包含后台权限:

private val REQUEST_LOCATION_PERMISSION = 1001

// 发起权限请求
fun requestLocationPermissions() {
    val permissions = arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_BACKGROUND_LOCATION
    )
    requestPermissions(permissions, REQUEST_LOCATION_PERMISSION)
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.29 06:38:11