前台服务调度问题:当App不在后台时AlarmManager无法启动前台服务

问题描述 投票:0回答:2

我在 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检查了日志,它没有显示任何与警报或前台服务未启动相关的错误消息。

我正在寻求有关警报为何未按预期触发前台服务的指导。如果您能提供任何见解、建议或解决此问题的潜在解决方案,我将不胜感激。

谢谢您的帮助。

java android kotlin android-service alarmmanager
2个回答
0
投票

当应用程序未在后台主动运行时,您用于安排前台服务的

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

注意:对于 API 级别 21 及以上,

JobScheduler
是安排作业的有效方法。作业可以设置为在重新启动后持续存在并遵循设备空闲状态。


0
投票
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 并设置间隔并启动前台服务

© www.soinside.com 2019 - 2024. All rights reserved.