前台服务调度问题:当应用程序不在后台时,AlarmManager无法启动前台服务。

3
我在我的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>


在应用程序处于前台或后台时,功能完全正常。

enter image description here

当应用程序在前台或后台时接收者: 在此输入图像描述

我使用Logcat检查了日志,没有显示与闹钟或前台服务未启动相关的错误消息。

我正在寻求关于为什么闹钟没有按预期触发前台服务的指导。我将非常感谢任何见解、建议或潜在解决方案来解决这个问题。

谢谢您的帮助。


抱歉,我没有完整阅读整个内容,只看了前几行,我有一个问题 - 为什么你要启动一个前台服务?如果你正在使用闹钟,请尝试将代码从服务移动到闹钟接收器,或者只需使用WorkManager进行定期工作。 - undefined
这个应用主要关注前台服务,我们需要一种方法来安排这个服务在一段时间范围内运行,即使应用程序不在后台运行。 有没有其他方法可以安排前台服务在凌晨2点到4点之间运行(例如时间)? - undefined
你有没有尝试使用精确的闹钟而不是不精确的闹钟?文档明确指出这是背景启动限制的例外情况 - undefined
@CommonsWare,我试过使用Schedule_exact_alarm,在一些设备上运行良好,即使应用程序不在后台也能启动前台服务。然而,在我的物理设备vivo1915(API 31)上无法正常工作。 - undefined
2个回答

1
你的实现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 / Background Work Overview中读到。

闹钟是一种特殊情况,不属于后台工作的一部分。你应该通过上述两种解决方案,协程和WorkManager来执行后台工作。

你应该只使用AlarmManager来安排精确的闹钟,如闹钟或日历事件。当使用AlarmManager来安排后台工作时,它会唤醒设备并退出Doze模式,因此可能对电池寿命和整体系统健康产生负面影响。这些影响由你的应用负责。

另请参阅"WorkManager vs AlarmManager,根据情况使用哪个?"

由于WorkManager被设计为与Doze模式和App Standby限制兼容,您可以使用OneTimeWorkRequestsetInitialDelay进行调度。
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);
}

注意:对于API级别21及以上,JobScheduler是一种有效的调度作业的方式。作业可以设置为在重新启动后持久存在,并且会尊重设备的空闲状态。

1
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
    )
}

使用AlarmManager和WorkManager结合来处理小于15分钟的间隔,对于大于15分钟的间隔,只使用WorkManager,并设置您的间隔并启动前台服务。

很好的解决方案,我会试试看。 @Ehsan,WorkManager 如何帮助我安排前台服务在一天中的特定时间? - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接