PDM · DAW 2

Aula 23 · Semana 10 · Terça, 10 de novembro de 2026 · 08:30 às 10:30

Trabalho em background e sprint 2

Teoria (30 minutos): execução em segundo plano: porque o sistema suspende ou termina a app, o que WorkManager e BGTaskScheduler garantem, e o que pode correr num isolate de background em Flutter. Prática: ponto de situação da sprint 2, demonstração do workmanager a atualizar a cache, e trabalho de grupo.

Review da sprint 2: quarta 18/11

No fim desta aula consegues

Porque o sistema limita o background

1. No ecrã Tudo corre: Timer, streams, animações, await http.get 2. Em segundo plano iOS: suspende em segundos Android: até faltar memória 3. Processo terminado Sem aviso; nada termina Volta do zero, em main() O que morre Timer, streams, animações, isolates um await http.get a meio: a app não é avisada O que sobrevive WorkManager · BGTaskScheduler · push silenciosa notificações e alarmes · o que está em disco (aula 16) O SO decide quando corre, não tu Pedes uma janela; o sistema escolhe a hora, junta com outras apps e pode adiar ou recusar.
Ao sair do ecrã a app não fica a correr: o que estava a meio morre, e só corre depois o que o sistema aceitou agendar.

Para os testes: uma app em segundo plano não está a correr; está suspensa ou morta, e o código que estava a meio não termina.

Analogia Uma app é uma loja num centro comercial. Aberta e com clientes é o primeiro plano; com as luzes apagadas mas o stock no lugar é o segundo plano ou a suspensão. De madrugada, o centro comercial pode despejar a loja sem avisar para dar o espaço a outra: é o processo terminado. Quando o cliente volta, a loja reabre como se nada fosse, e o que não estava guardado no armazém (o disco) perdeu-se.

Android: WorkManager, foreground services, Doze e buckets

MecanismoO que éO que a tua app tem de saber
WorkManagerTrabalho diferido ou periódico, com restrições (rede, carregador, bateria). Sobrevive a reinícios.Mínimo 15 min entre execuções. O sistema escolhe a hora. A tarefa deve ser curta e idempotente.
Foreground serviceProcesso a correr com notificação permanente visível.Só para o que o utilizador vê a acontecer: música, navegação, upload. Android 14 exige tipo declarado e permissão própria.
AlarmManagerAcordar a app a uma hora.Exato exige SCHEDULE_EXACT_ALARM (12+); no 14 só despertadores e calendários a têm por defeito. Inexato pode atrasar minutos.
DozeEcrã desligado e telemóvel parado: rede, jobs e alarmes adiados para janelas de manutenção cada vez mais espaçadas.A tua tarefa "de 6 em 6 horas" pode correr às 8 ou às 9. Não há volta a dar, e é assim que deve ser.
App Standby bucketsActive, working set, frequent, rare, restricted, consoante quanto o utilizador usa a app.App raramente aberta: poucos jobs por dia. O utilizador pode ainda pô-la em "restrito" nas definições de bateria.

Para os testes: o WorkManager garante que a tarefa corre, não quando; o Doze e os buckets decidem o quando.

Analogia Registar uma tarefa no WorkManager é deixar um recado ao porteiro do prédio, o sistema operativo: «quando puderes, vai regar as plantas». O porteiro decide a hora, junta os recados de vários moradores para poupar viagens, e se o prédio estiver em modo poupança (Doze) só passa de madrugada. Nenhum morador pode exigir «às 20:00 em ponto»; para isso existem o alarme e a notificação agendada.

iOS: BGAppRefreshTask, BGProcessingTask e silent push

Para os testes: no iOS não há tarefa periódica com hora; o sistema aprende os hábitos do utilizador e decide quando acordar a app.

No Flutter: workmanager, background_fetch e o isolate

Para os testes: o callback de background corre num isolate novo, sem árvore de widgets e sem o estado da app; só o disco é partilhado.

Padrões: sincronizar de madrugada, não fazer polling, respeitar o utilizador

Para os testes: trabalho em background é curto, idempotente e tolera ser adiado ou repetido; se precisa de hora exata, não é background, é um alarme ou uma notificação agendada.

Sprint 2: ponto de situação (semana 2 de 2)

Cada grupo, um minuto, em pé, sem ligar o portátil:

  1. O que está merged em main e corre no telemóvel neste momento. A notificação de aviso e o deep link de ontem já estão em PR?
  2. O que está em PR aberto e quem está a rever.
  3. O que entra na review de quarta 18/11 e o que passa para a sprint 3. Decidir hoje é melhor do que descobrir na terça.
  4. Um bloqueio, se houver. O docente resolve primeiro os bloqueios, depois circula pela ordem do quadro.
Review da sprint 2: quarta 18/11 Demo de 5 minutos por grupo, no telemóvel, a partir de main. Candidatos: favoritos sincronizados, cache offline com "atualizado há X", mapa com localidade mais próxima, notificação de aviso com deep link. Nem tudo é obrigatório; o que entrar tem os quatro estados de UI tratados.

Demo (1 de 2): registar a tarefa periódica no Android

import 'package:workmanager/workmanager.dart';
import 'background/tarefas_background.dart'; // callbackDispatcher e tarefaCache

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Workmanager().initialize(callbackDispatcher); // função de topo: slide seguinte

  // Android: de 6 em 6 horas, só com rede e bateria não fraca. O sistema escolhe a hora.
  // O nome único evita duplicar: registar outra vez com o mesmo nome mantém a existente.
  await Workmanager().registerPeriodicTask(
    tarefaCache, // uniqueName: para cancelar e não duplicar
    tarefaCache, // taskName: o que chega ao callback
    frequency: const Duration(hours: 6),
    constraints: Constraints(
      networkType: NetworkType.connected,
      requiresBatteryNotLow: true,
    ),
    backoffPolicy: BackoffPolicy.exponential, // falhou? repete com espera crescente
  );

  // Testar já, só em desenvolvimento: registerOneOffTask('teste', tarefaCache, initialDelay: 10 s)
  runApp(/* ...como na aula 22 */);
}

Demo (2 de 2): o callback de topo, num isolate sem a app

import 'package:workmanager/workmanager.dart';
const tarefaCache = 'pt.uac.tempoacores.atualizarCache';

/// Chamada pelo nativo num isolate novo, sem a app: de topo, e com o pragma
/// para o tree-shaking não a apagar em release.
@pragma('vm:entry-point')
void callbackDispatcher() {
  Workmanager().executeTask((tarefa, dados) async {
    if (tarefa != tarefaCache) return true; // tarefa desconhecida: não repetir
    try { // sem Provider nem BuildContext: montamos o que precisamos à mão
      final token = await const FlutterSecureStorage().read(key: 'token');
      if (token == null) return true; // sem sessão, nada a fazer
      final api = ApiClient(token: token);
      final cache = PrevisoesCache(AppDatabase()); // drift, aula 16
      final favoritos = await FavoritosLocais.ids(); // shared_preferences
      for (final id in favoritos) {
        await cache.guardar(id, await api.previsao(id)); // GET /v1/forecast/{id}
      }
      await AvisosBackground(api, cache).verificarENotificar(favoritos); // aula 22
      return true;
    } catch (e) {
      return false; // o WorkManager repete com backoff exponencial
    }
  });
}

Demo: o que interessa

Exercício 1 (extra, só depois do essencial da sprint) Tarefa periódica no Android que atualiza a cache dos favoritos e dispara a notificação de aviso da aula 22 sem a app aberta. Interruptor "Atualizar em segundo plano" nas definições que faz cancelByUniqueName. Testa com initialDelay de 10 s, a app fechada e o telemóvel bloqueado.

Até amanhã

Para fazer

  • Sprint 2 continua: PRs de ontem (notificação de aviso e deep link) revistos e merged; review na quarta 18/11 a partir de main.
  • Decidir hoje o que entra na review e fechar, com justificação, as issues que passam para a sprint 3.
  • Amanhã às 16:00: hardware e sensores. Traz o telemóvel com NFC ligado, se tiver.

Para ler

  • Background tasks overview no Android: a árvore de decisão entre WorkManager, foreground service e alarmes.
  • Persistent work with WorkManager: restrições, trabalho periódico, o mínimo de 15 minutos e o backoff em caso de falha.
  • BackgroundTasks na Apple: BGAppRefreshTask, BGProcessingTask e como o sistema decide quando as corre.
  • Processes and app lifecycle: a hierarquia de processos do Android e a ordem pela qual o sistema os termina quando falta memória.
  • workmanager: a configuração por plataforma, as restrições e o que o iOS não suporta.