Skip to content

Impedir loop de alarm clock e try-catch no attachForegroundTask - #9

Merged
brunonahornygaudium merged 4 commits into
masterfrom
fix/crashes-foreground-service
Oct 20, 2026
Merged

brunonahornygaudium merged 4 commits into
masterfrom
fix/crashes-foreground-service

Conversation

@JCPistori

Copy link
Copy Markdown
Collaborator

No description provided.

JCPistori and others added 3 commits September 22, 2026 11:10
Marca com [GAUDIUM/OEM] e [GAUDIUM] os pontos do fork que existem por causa de
dispositivos especificos ou que divergem de proposito do upstream, para que um
merge futuro nao os remova por engano:

- startForeground() nos caminhos de update e stop (Motorola e outros OEMs)
- createNotificationChannel() antes do startForeground()
- shouldReattach=false no catch do onStartCommand
- guarda de lateinit em foregroundTaskOptions no onDestroy
- som em thread separada (ANR em playCustomSound)
- iOS: som customizado com playSound=false, reset de content.sound, notificacao
  com corpo vazio

Somente comentarios, sem mudanca de comportamento.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
O ciclo "onDestroy -> setRestartAlarm(5s) -> RestartReceiver ->
startForegroundService -> startForeground() lanca -> stopForegroundService ->
onDestroy" nao tinha teto: enquanto a causa raiz persistisse (cota de FGS
esgotada, start de FGS nao permitido em background) o alarme era reagendado a
cada 5 segundos indefinidamente, drenando bateria e acordando o aparelho.

- Adiciona RestartBudget: backoff de 5s/30s/2min/10min/30min, teto de 5
  tentativas e janela de 1h que devolve o orcamento ao cheio.
- Zera o orcamento quando o startForeground() realmente passa, e tambem em um
  start explicito vindo do Dart.
- onTaskRemoved passa a compartilhar o mesmo orcamento (mantendo 1s na primeira
  tentativa), senao viraria a nova fonte do loop.
- Troca setAlarmClock por setExactAndAllowWhileIdle: os dois concedem a janela
  de allowlist que permite iniciar FGS em background no Android 12+, mas o
  setAlarmClock e tratado como despertador do usuario e chega a exibir o icone
  de alarme na status bar.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
O caminho API_STOP do onStartCommand fica fora do try/catch, entao uma excecao
em attachForegroundTask() (startForeground() recusado pelo sistema) abortava o
stopForegroundService antes do stopSelf(): o servico continuava vivo e o
_isRunningServiceState seguia true, fazendo o isRunningService() do Dart mentir
e o start seguinte falhar com ServiceAlreadyStartedException.

- Envolve o reattach em try/catch, dentro de stopForegroundService, cobrindo de
  uma vez os caminhos de onStartCommand, onDestroy e onTimeout.
- Move stopSelf() e a atualizacao do state flow para um finally.
- Remove Log.d ruidoso com TAG truncada no update do ForegroundServiceManager.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@JCPistori JCPistori self-assigned this Sep 22, 2026
@gaudinho

gaudinho Bot commented Sep 22, 2026 •

Copy link
Copy Markdown

✅ Review postada — #9 (review)

job_id: flutter_foreground_task-9-1790096555713-d601

gaudinho[bot]
gaudinho Bot previously approved these changes Sep 22, 2026

@gaudinho gaudinho Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix bem fundamentado: adiciona orçamento com backoff exponencial (RestartBudget) para conter o loop de restart via AlarmManager e envolve o reattach de OEM em try/catch pra garantir que o teardown do service sempre aconteça, mesmo quando attachForegroundTask() falha. Os comentários no código documentam a cadeia causal (por quê do loop, por quê do try/catch) de forma consistente com o histórico de workarounds de OEM já existente no arquivo.

with(prefs.edit()) {
putInt(PrefsKey.RESTART_ATTEMPT_COUNT, attempts + 1)
putLong(PrefsKey.RESTART_WINDOW_START, if (isNewWindow) now else windowStart)
commit()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit (Performance) — commit() é síncrono e bloqueia a thread principal (chamado em onDestroy/onTaskRemoved, caminhos sensíveis a ANR). Se a durabilidade imediata não for estritamente necessária (o alarme é local, não lê os prefs de outro processo), apply() evita o I/O bloqueante. Se a escolha foi deliberada para garantir que o contador sobreviva a um kill do processo logo após, vale um comentário explicando — hoje não é óbvio pra quem for mexer depois.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

O apply() enfileira a escrita, que ainda vai ocorrer na thread principal dependendo do ciclo de vida

@JCPistori

Copy link
Copy Markdown
Collaborator Author

CI tá falhando com a versão do gradle, que subiu na PR #10. Essa foi criada apenas para visualizar as alterações pontuais das correções. A PR 10 que será a liberada.
Captura de Tela 2026-09-22 às 14 51 35

@brunonahornygaudium
brunonahornygaudium merged commit 37d548a into master Oct 20, 2026
2 of 3 checks passed
@brunonahornygaudium
brunonahornygaudium deleted the fix/crashes-foreground-service branch October 20, 2026 19:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants