我在 Android 应用程序中使用 AlarmManager 调度前台服务时遇到问题。尽管使用警报设置代码在特定时间触发前台服务,但当应用程序不在后台时,警报无法启动前台服务 (当应用程序位于前台或后台时,它工作正常)。
我的应用程序需要在一天的特定时间范围内执行前台服务。为了实现这一目标,我使用 AlarmManager 来安排服务以所需的时间间隔启动和停止。
除了alarmManager之外,还有哪些不同的方式来安排前台服务。
TimeBasedScheduler 类:
class TimeBasedScheduler : BroadcastReceiver() {
private val TAG = "TimeBasedScheduler"
override fun onReceive(context: Context, intent: Intent) {
val action = intent.action
Log.d(TAG, "Received action: $action")
if (action != null) {
when (action) {
"START_SERVICE" -> {
Log.d(TAG, "Initializing socket for START_SERVICE")
initializeSocket(context)
// Now execution is started and resumed
LocalPrefrenceUtils.insertDataInBoolean(context, AppConstants.BACKGROUND_WORK_ALLOWED_KEY_FOR_LOCAL_PREFERENCE, true)
LocalPrefrenceUtils.insertDataInBoolean(context, AppConstants.BACKGROUND_WORK_TEMPORARILY_PAUSED_KEY_FOR_LOCAL_PREFERENCE, false
)
}
"STOP_SERVICE" -> {
Log.d(TAG, "Stopping service for STOP_SERVICE")
LocalPrefrenceUtils.insertDataInBoolean(
context,
AppConstants.BACKGROUND_WORK_TEMPORARILY_PAUSED_KEY_FOR_LOCAL_PREFERENCE,
true
)
val stopIntent = Intent(context, SocketManagerService::class.java)
context.stopService(stopIntent)
}
}
}
}
private fun initializeSocket(context: Context?) {
Log.d(TAG, "Initializing socket")
val intent = Intent(context, SocketManagerService::class.java)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Log.d(TAG, "Starting foreground service")
context?.startForegroundService(intent)
} else {
Log.d(TAG, "Starting service")
context?.startService(intent)
}
}
}
设置活动类:
private fun setupForegroundServiceAlarms() {
val TAG = "TaskBasedScheduler"
// Get the start and end times from preferences
val startTime = LocalPrefrenceUtils.getStartTime(this)
val endTime = LocalPrefrenceUtils.getEndTime(this)
Log.d(TAG, "Start Time: $startTime")
Log.d(TAG, "End Time: $endTime")
// Calculate start and end times based on step sizes and special cases for value 48
val startHour = 7 // Starting hour (7:00 am)
val stepSizeMinutes = 30 // Step size in minutes
val startHourForAlarms = startHour + (stepSizeMinutes * startTime) / 60
val endHourForAlarms = if (endTime == 48) startHour + (stepSizeMinutes * endTime) / 60 - 1 else startHour + (stepSizeMinutes * endTime) / 60
val startMinute = (stepSizeMinutes * startTime) % 60
val endMinute = if (endTime == 48) ((stepSizeMinutes * endTime) % 60) - 1 else ((stepSizeMinutes * endTime) % 60)
// Set the Calendar instances for start and end times
val startCalendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, startHourForAlarms)
set(Calendar.MINUTE, startMinute)
set(Calendar.SECOND, 0)
}
val endCalendar = Calendar.getInstance().apply {
timeInMillis = System.currentTimeMillis()
set(Calendar.HOUR_OF_DAY, endHourForAlarms)
set(Calendar.MINUTE, endMinute)
set(Calendar.SECOND, 0)
}
Log.d(TAG, "Start Calendar Time: ${startCalendar.time}")
Log.d(TAG, "End Calendar Time: ${endCalendar.time}")
// Cancel previously set alarms
alarmManager.cancel(startPendingIntent)
alarmManager.cancel(endPendingIntent)
Log.d(TAG, "Canceled previous alarms")
// Create intents for starting and stopping the service
val startIntent = Intent(this, TimeBasedScheduler::class.java).apply {
action = "START_SERVICE"
}
val endIntent = Intent(this, TimeBasedScheduler::class.java).apply {
action = "STOP_SERVICE"
}
// Create pending intents for start and end actions
startPendingIntent = PendingIntent.getBroadcast(this, 0, startIntent, PendingIntent.FLAG_IMMUTABLE)
endPendingIntent = PendingIntent.getBroadcast(this, 1, endIntent, PendingIntent.FLAG_IMMUTABLE)
Log.d(TAG, "Created pending intents")
// Set alarms using AlarmManager for daily repetition
alarmManager.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
startCalendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
startPendingIntent
)
alarmManager.setInexactRepeating(
AlarmManager.RTC_WAKEUP,
endCalendar.timeInMillis,
AlarmManager.INTERVAL_DAY,
endPendingIntent
)
Log.d(TAG, "Alarms set for start: ${startCalendar.time}, end: ${endCalendar.time}")
}
以及清单相关代码:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="com.android.alarm.permission.SET_ALARM" />
<receiver android:name=".schedulers.TimeBasedScheduler"
android:enabled="true"
android:exported="true">
<intent-filter>
<action android:name="START_SERVICE" />
<action android:name="STOP_SERVICE" />
</intent-filter>
</receiver>
当应用程序位于前台或后台时,效果非常好:
我使用Logcat检查了日志,它没有显示任何与警报或前台服务未启动相关的错误消息。
我正在寻求有关警报为何未按预期触发前台服务的指导。如果您能提供任何见解、建议或解决此问题的潜在解决方案,我将不胜感激。
谢谢您的帮助。
当应用程序未在后台主动运行时,您用于安排前台服务的
AlarmManager
实现会遇到问题。由于各种系统限制,这是 Android 开发中的一个常见挑战,尤其是在较新的 Android 版本上。
你有:
+---------------------+ +---------------------------+ +---------------------------+
| SettingActivity | | TimeBasedScheduler | | SocketManagerService |
| - setupForeground- | | - onReceive() | | - Foregrnd Service Logic |
| ServiceAlarms() |----->| * START_SERVICE Action |----->| |
| | | * STOP_SERVICE Action | | |
+---------------------+ +---------------------------+ +---------------------------+
| | |
| Sets Alarms | Starts/Stops Foreground |
| for START/STOP | Service based on |
| | received action |
+----------------------------------+----------------------------+
我阅读了Android /后台工作概述
警报是一种特殊用例,不属于后台工作的一部分。您应该通过上面概述的两个解决方案执行后台工作:协程和
。WorkManager
您只能将
用于安排精确的闹钟,例如闹钟或日历事件。使用AlarmManager
安排后台工作时,会将设备从休眠模式唤醒,因此使用它会对电池寿命和整体系统运行状况产生负面影响。您的应用程序应对此类影响负责。AlarmManager
WorkManager
vs AlarmManager
,根据情况使用什么?”。
WorkManager
被设计为兼容 Doze 模式和应用程序待机限制,因此您可以使用 OneTimeWorkRequest
和 setInitialDelay
进行调度。
public class TimeBasedScheduler extends Worker {
public TimeBasedScheduler(@NonNull Context context, @NonNull WorkerParameters workerParams) {
super(context, workerParams);
}
@NonNull
@Override
public Result doWork() {
// Your service logic here
return Result.success();
}
}
并将其安排在您的
SettingActivity
中:
private void scheduleWork() {
// Define constraints like network type, charging status, etc.
Constraints constraints = new Constraints.Builder()
.build();
// Schedule your work
OneTimeWorkRequest workRequest = new OneTimeWorkRequest.Builder(TimeBasedScheduler.class)
.setInitialDelay(1, TimeUnit.HOURS) // Set the delay as per your requirement
.setConstraints(constraints)
.build();
WorkManager.getInstance(this).enqueue(workRequest);
}
JobScheduler
是安排作业的有效方法。作业可以设置为在重新启动后持续存在并遵循设备空闲状态。
try
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
startCalendar.timeInMillis,
startPendingIntent
)
alarmManager.setExactAndAllowWhileIdle(
AlarmManager.RTC_WAKEUP,
endCalendar.timeInMillis,
endPendingIntent
)
} else {
// For versions below M, use setExact
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
startCalendar.timeInMillis,
startPendingIntent
)
alarmManager.setExact(
AlarmManager.RTC_WAKEUP,
endCalendar.timeInMillis,
endPendingIntent
)
}
并将警报管理器与 Workmanager 一起使用,间隔时间小于 15 分钟,大于 15 分钟,仅使用 Workmanager 并设置间隔并启动前台服务