На некоторых устройствах (например, Google Pixel 8a) уведомления не приходят, база данных не обновляется автоматически каждые несколько часов или уведомления приходят очень поздно, когда телефон заблокирован или находится в режиме ожидания.
На других устройствах (например, телефонах Samsung) все работает отлично:
- Уведомления по расписанию доставляются на правильное время.
- База данных обновляется примерно каждые 4 часа, как и предполагалось.
Когда пользователь добавляет аниме, приложение планирует уведомление о времени выпуска следующего эпизода.
У меня также есть задача WorkManager, которая запускается каждые 4 часа, чтобы:
- Проверить, есть ли время выпуска изменено.
- При необходимости обновите базу данных Firebase.
- Соответствующим образом перенесите уведомления.
Вопрос
Есть ли еще надежный способ обеспечить правильную периодическую фоновую работу и запланированные уведомления даже на устройствах с более строгими ограничениями Doze или заряда батареи?
В настоящее время я использую WorkManager.
Следует ли мне рассмотреть альтернативы или дополнительные конфигурации, такие как setExactAndAllowWhileIdle(), ForegroundService или любой другой рекомендуемый подход?
Если возможно, может ли кто-нибудь предоставить базовый пример реализации, показывающий, как:
Запускать надежную фоновую задачу которое выполняется даже в режиме ожидания, и запланировать уведомление, которое будет срабатывать точно в заданное время (даже если устройство находится в режиме ожидания)?
Я покажу, как оно у меня смонтировано:
@HiltAndroidApp
class NotakuApplication : Application(), Configuration.Provider {
@Inject
lateinit var workerFactory: HiltWorkerFactory
override val workManagerConfiguration: Configuration
get() = Configuration.Builder()
.setWorkerFactory(workerFactory)
.setMinimumLoggingLevel(Log.ERROR)
.build()
override fun onCreate() {
super.onCreate()
launchPeriodicSyncWorker()
launchImmediateSyncWorker()
}
private fun launchPeriodicSyncWorker() {
val workRequest = PeriodicWorkRequestBuilder(
4, TimeUnit.HOURS
)
.setInitialDelay(1, TimeUnit.MINUTES)
.build()
WorkManager.getInstance(this).enqueueUniquePeriodicWork(
"anime_sync_and_reminder_work",
ExistingPeriodicWorkPolicy.KEEP,
workRequest
)
}
private fun launchImmediateSyncWorker() {
val oneTimeWork = OneTimeWorkRequestBuilder().build()
WorkManager.getInstance(this)
.enqueueUniqueWork(
"immediate_anime_sync_and_reminder_work",
ExistingWorkPolicy.REPLACE,
oneTimeWork
)
}
}
@HiltWorker
class ReminderWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val title = inputData.getString("title") ?: return Result.failure()
val episodeNumber = inputData.getInt("episodeNumber", 0)
val episodeDateMillis = inputData.getLong("episodeDateMillis", System.currentTimeMillis())
showNotification(applicationContext, title, episodeNumber, episodeDateMillis)
return Result.success()
}
companion object {
fun showNotification(context: Context, title: String, episodeNumber: Int, episodeDateMillis: Long) {
val formatter = DateTimeFormatter.ofPattern("HH:mm")
.withLocale(Locale.getDefault())
.withZone(ZoneId.systemDefault())
val formattedTime = formatter.format(Instant.ofEpochMilli(episodeDateMillis))
val message = "¡El episodio $episodeNumber de $title se estrenará a las $formattedTime!"
val channelId = "notaku_channel"
val notificationManager = context.getSystemService(NotificationManager::class.java)
if (notificationManager.getNotificationChannel(channelId) == null) {
val channel = NotificationChannel(
channelId,
"Notaku Notifications",
NotificationManager.IMPORTANCE_DEFAULT
)
notificationManager.createNotificationChannel(channel)
}
val builder = NotificationCompat.Builder(context, channelId)
.setSmallIcon(R.drawable.notaku_logo_black)
.setContentTitle(title)
.setContentText(message)
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.setAutoCancel(true)
notificationManager.notify(System.currentTimeMillis().toInt(), builder.build())
}
}
}
@HiltWorker
class AnimeSyncAndReminderWorker @AssistedInject constructor(
@Assisted context: Context,
@Assisted workerParams: WorkerParameters,
private val syncUserTrackedAnimesUseCase: SyncUserTrackedAnimesUseCase,
private val getTrackedAnimesUseCase: GetTrackedAnimesUseCase,
private val getUserPreferencesUseCase: GetUserPreferencesUseCase
) : CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
return try {
syncUserTrackedAnimesUseCase.execute()
scheduleEpisodeReminders(getTrackedAnimesUseCase.execute())
Result.success()
} catch (e: Exception) {
e.printStackTrace()
Result.retry()
}
}
private suspend fun scheduleEpisodeReminders(animes: List) {
val context = applicationContext
val workManager = WorkManager.getInstance(context)
val reminderMinutes = getUserReminderMinutes()
val now = System.currentTimeMillis()
for (anime in animes) {
val episodeDateMillis = parseDateToMillis(anime.episodeDate) ?: continue
val delayMillis = episodeDateMillis - now - reminderMinutes * 60_000
val workName = "reminder_${anime.title}_${anime.episodeNumber}"
val data = workDataOf(
"title" to anime.title,
"episodeNumber" to anime.episodeNumber,
"episodeDateMillis" to episodeDateMillis
)
val existing = workManager.getWorkInfosForUniqueWork(workName).get()
val alreadyScheduled = existing.any {
it.state == WorkInfo.State.ENQUEUED &&
it.outputData.getLong("episodeDateMillis", 0) == episodeDateMillis
}
if (alreadyScheduled) {
Log.d(TagHandler.NOTAKU_INFO, "
continue
}
if (delayMillis > 0) {
val workRequest = OneTimeWorkRequestBuilder()
.setInitialDelay(delayMillis, TimeUnit.MILLISECONDS)
.setInputData(data)
.build()
workManager.enqueueUniqueWork(workName, ExistingWorkPolicy.REPLACE, workRequest)
}
}
}
private fun parseDateToMillis(dateString: String): Long? {
return try {
val odt = OffsetDateTime.parse(dateString)
odt.toInstant().toEpochMilli()
} catch (e: Exception) {
Log.e(TagHandler.NOTAKU_ERROR, "Error parsing date: $dateString", e)
null
}
}
private suspend fun getUserReminderMinutes(): Int {
return getUserPreferencesUseCase()
.firstOrNull()
?.notificationTime ?: 30
}
}
Подробнее здесь: https://stackoverflow.com/questions/798 ... ces-workma
Мобильная версия