Por que Tarefas em Segundo Plano São Essenciais em Apps Modernos
Vamos ser sinceros: em 2026, ninguém tem paciência pra abrir um app e esperar os dados carregarem do zero. Os usuários esperam que tudo esteja atualizado, sincronizado e pronto pra uso. E sabe o que torna isso possível? Tarefas em segundo plano — basicamente, pedaços de código que rodam enquanto o app não está na tela, sem nenhuma interface visível.
O Expo SDK 53 trouxe o expo-background-task, uma API moderna que veio pra substituir o antigo expo-background-fetch. Ela usa as APIs nativas mais recentes de cada plataforma: o WorkManager no Android e o BGTaskScheduler no iOS.
Neste guia, você vai aprender a implementar tarefas em segundo plano de forma confiável, respeitando as restrições (cada vez mais rigorosas) dos sistemas operacionais atuais.
O que é o expo-background-task
O expo-background-task é um módulo do Expo que fornece uma API pra executar tarefas diferíveis em segundo plano, otimizando o consumo de bateria. Diferente das abordagens antigas que dependiam de APIs depreciadas, esse módulo foi construído sobre fundações estáveis e modernas — o que, na prática, significa menos dor de cabeça com atualizações do sistema operacional.
Principais Características
- WorkManager (Android): Permite especificar um intervalo mínimo de execução com garantia de entrega, mesmo após reinicializações do dispositivo
- BGTaskScheduler (iOS): Usa o sistema inteligente de agendamento da Apple, que considera bateria, rede disponível e padrões de uso do usuário
- Arquitetura de worker único: Múltiplas tarefas JavaScript rodam sequencialmente por um único worker nativo, respeitando os limites das plataformas
- Conectividade de rede garantida: As tarefas são configuradas pra exigir conexão de rede antes da execução
- Compatibilidade com CNG: Configurações nativas são aplicadas automaticamente via prebuild
Migração do expo-background-fetch
Se você já usa o expo-background-fetch, pode ficar tranquilo — a migração é bem simples. A API do expo-background-task é quase idêntica, com ajustes mínimos nos parâmetros de registerTaskAsync.
Um detalhe importante: o expo-background-fetch foi oficialmente depreciado e não vai receber mais atualizações a partir do Expo SDK 54. Então vale migrar logo.
As principais diferenças são:
- Substituir importações de
expo-background-fetchporexpo-background-task - Usar
BackgroundTaskResultao invés deBackgroundFetchResultnos retornos das tarefas - O parâmetro
minimumIntervalagora é especificado em minutos (antes era em segundos — cuidado pra não confundir!) - Remover opções específicas do Android como
stopOnTerminateestartOnBoot, que não são mais necessárias
Instalação e Configuração
Instalando os pacotes necessários
O expo-background-task trabalha em conjunto com o expo-task-manager pra definir e gerenciar tarefas. Instale os dois:
npx expo install expo-background-task expo-task-manager
Configuração no iOS (CNG/Prebuild)
Se você usa Continuous Native Generation (CNG) com npx expo prebuild, as configurações no Info.plist são aplicadas automaticamente. Nenhuma configuração manual necessária — só alegria.
Configuração Manual no iOS
Pra projetos que não usam CNG, você vai precisar adicionar manualmente as seguintes entradas ao Info.plist:
<key>UIBackgroundModes</key>
<array>
<string>processing</string>
</array>
<key>BGTaskSchedulerPermittedIdentifiers</key>
<array>
<string>com.expo.modules.backgroundtask.processing</string>
</array>
Essas entradas habilitam a capacidade de processamento em segundo plano e registram o identificador de tarefa permitido pelo sistema.
Configuração no Android
No Android, nenhuma configuração extra é necessária. O módulo cuida de tudo usando o WorkManager por baixo dos panos.
Implementação Passo a Passo
Passo 1: Definir a Tarefa no Escopo Global
Esse é um ponto que pega muita gente de surpresa: a definição da tarefa precisa estar no escopo global do seu bundle JavaScript — fora de qualquer componente React. Por quê? Porque quando o sistema inicia seu app em segundo plano, o JavaScript é carregado, a tarefa roda e o processo morre. Nenhuma view é montada.
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
const TAREFA_SYNC = 'tarefa-sincronizacao-dados';
// Definição DEVE estar no escopo global
TaskManager.defineTask(TAREFA_SYNC, async () => {
try {
const agora = Date.now();
console.log(
`Tarefa em segundo plano executada em: ${new Date(agora).toISOString()}`
);
// Sua lógica de sincronização aqui
const response = await fetch('https://api.seuservidor.com/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ ultimaSync: agora }),
});
if (!response.ok) {
return BackgroundTask.BackgroundTaskResult.Failed;
}
return BackgroundTask.BackgroundTaskResult.Success;
} catch (error) {
console.error('Erro na tarefa em segundo plano:', error);
return BackgroundTask.BackgroundTaskResult.Failed;
}
});
Passo 2: Registrar a Tarefa
Com a tarefa definida, agora você precisa registrá-la pra que o sistema saiba quando executá-la. Essa parte pode ficar dentro de um componente React normalmente:
async function registrarTarefaBackground() {
return BackgroundTask.registerTaskAsync(TAREFA_SYNC, {
minimumInterval: 15, // intervalo mínimo em minutos
});
}
async function cancelarTarefaBackground() {
return BackgroundTask.unregisterTaskAsync(TAREFA_SYNC);
}
O minimumInterval define o intervalo mínimo entre execuções. O valor mínimo aceito é 15 minutos, e o padrão é 12 horas. Mas aqui vai um ponto crucial: esse é um limite inferior. O sistema pode decidir executar sua tarefa bem depois, dependendo das condições do dispositivo.
Passo 3: Criar o Componente de Controle
Aqui vai um exemplo completo de componente que permite ao usuário ativar e desativar a tarefa em segundo plano. Eu particularmente gosto de ter esse tipo de controle visível no app — dá mais transparência pro usuário:
import { useEffect, useState } from 'react';
import { StyleSheet, Text, View, Button, Alert } from 'react-native';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
const TAREFA_SYNC = 'tarefa-sincronizacao-dados';
export default function TelaBackgroundTask() {
const [registrada, setRegistrada] = useState(false);
const [status, setStatus] = useState(null);
useEffect(() => {
atualizarStatus();
}, []);
const atualizarStatus = async () => {
const statusAtual = await BackgroundTask.getStatusAsync();
setStatus(statusAtual);
const estaRegistrada = await TaskManager.isTaskRegisteredAsync(TAREFA_SYNC);
setRegistrada(estaRegistrada);
};
const alternarTarefa = async () => {
if (!registrada) {
await registrarTarefaBackground();
Alert.alert('Sucesso', 'Tarefa em segundo plano ativada!');
} else {
await cancelarTarefaBackground();
Alert.alert('Sucesso', 'Tarefa em segundo plano desativada.');
}
await atualizarStatus();
};
return (
<View style={styles.container}>
<Text style={styles.titulo}>Tarefas em Segundo Plano</Text>
<Text style={styles.info}>
Status: {status === BackgroundTask.BackgroundTaskStatus.Available
? 'Disponível'
: 'Restrito'}
</Text>
<Text style={styles.info}>
Tarefa: {registrada ? 'Ativa' : 'Inativa'}
</Text>
<Button
disabled={status === BackgroundTask.BackgroundTaskStatus.Restricted}
title={registrada ? 'Desativar Tarefa' : 'Ativar Tarefa'}
onPress={alternarTarefa}
/>
<Button
title='Verificar Status'
onPress={atualizarStatus}
/>
</View>
);
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20 },
titulo: { fontSize: 22, fontWeight: 'bold', marginBottom: 20 },
info: { fontSize: 16, marginBottom: 10 },
});
Referência Completa da API
Pra quem gosta de ter tudo documentado (eu sou dessa turma), aqui vai a referência completa do módulo expo-background-task.
Funções
getStatusAsync()— Retorna o status de disponibilidade da API. RetornaBackgroundTaskStatus.AvailableouBackgroundTaskStatus.RestrictedregisterTaskAsync(taskName, options?)— Registra uma tarefa em segundo plano. A tarefa precisa ter sido previamente definida comTaskManager.defineTaskunregisterTaskAsync(taskName)— Cancela o registro de uma tarefa, impedindo futuras execuçõestriggerTaskWorkerForTestingAsync()— Dispara manualmente todas as tarefas registradas pra fins de teste. Funciona apenas no modo de desenvolvimento
Enums
BackgroundTaskResult.Success(valor: 1) — Tarefa concluída com sucessoBackgroundTaskResult.Failed(valor: 2) — A tarefa encontrou um erroBackgroundTaskStatus.Restricted(valor: 1) — Tarefas em segundo plano indisponíveisBackgroundTaskStatus.Available(valor: 2) — Tarefas em segundo plano disponíveis
Event Listener (apenas iOS)
O addExpirationListener(listener) permite registrar um callback chamado quando o sistema decide encerrar as tarefas em segundo plano. Use pra liberar recursos ou salvar o estado atual. O task runner é reagendado automaticamente após a expiração.
import * as BackgroundTask from 'expo-background-task';
const subscription = BackgroundTask.addExpirationListener(() => {
console.log('Sistema solicitou encerramento da tarefa. Salvando estado...');
// Lógica de cleanup aqui
});
// Para remover o listener depois:
subscription.remove();
Comportamento por Plataforma
Aqui é onde as coisas ficam interessantes — e um pouco frustrantes, honestamente. Android e iOS têm comportamentos bem diferentes quando o assunto é tarefas em segundo plano.
Android (WorkManager)
No Android, o WorkManager é mais previsível e "amigável" pro desenvolvedor:
- Intervalo mínimo de 15 minutos entre execuções
- Tarefas sobrevivem a reinicializações do dispositivo
- Funciona tanto em emuladores quanto em dispositivos físicos
- A tarefa roda quando o intervalo mínimo é atingido e as condições de rede são satisfeitas
iOS (BGTaskScheduler)
No iOS, o sistema tem muito mais controle sobre quando suas tarefas rodam:
- O sistema decide o melhor momento com base em bateria, rede e hábitos do usuário
- Tarefas podem ser adiadas significativamente — às vezes pro período noturno
- A API de Background Tasks não funciona em simuladores iOS — só em dispositivos físicos
- Se o usuário desativar "Atualização em segundo plano" nas configurações, nada vai rodar
- Tarefas podem ser interrompidas a qualquer momento — use o
addExpirationListenerpra lidar com isso
Testando Tarefas em Segundo Plano
Testar tarefas em segundo plano é, pra ser bem honesto, um dos maiores desafios desse tipo de implementação. Você depende do agendamento do sistema operacional, que pode levar horas. Felizmente, o expo-background-task oferece uma saída pra isso.
Usando triggerTaskWorkerForTestingAsync
A ideia é simples: adicione um botão de debug que dispara as tarefas manualmente. Eu sempre faço isso nos meus projetos e recomendo fortemente:
import * as BackgroundTask from 'expo-background-task';
import { Button } from 'react-native';
function BotaoDebug() {
const dispararTarefa = async () => {
try {
const resultado = await BackgroundTask.triggerTaskWorkerForTestingAsync();
console.log('Tarefa disparada com sucesso:', resultado);
} catch (error) {
console.error('Erro ao disparar tarefa:', error);
}
};
// Exibir apenas em modo de desenvolvimento
if (!__DEV__) return null;
return (
<Button
title='[DEV] Disparar Tarefa em Background'
onPress={dispararTarefa}
/>
);
}
Depuração no Android
No Android, dá pra usar o adb pra inspecionar tarefas do WorkManager:
adb shell dumpsys jobscheduler | grep -i expo
Depuração no iOS
No iOS, infelizmente não existe ferramenta similar ao adb. Use o triggerTaskWorkerForTestingAsync mesmo. Se aparecer este erro no console do Xcode:
No task request with identifier com.expo.modules.backgroundtask.processing has been scheduled
Significa que você precisa rodar npx expo prebuild pra aplicar as configurações de segundo plano ao projeto nativo.
Casos de Uso Práticos
Sincronização de Dados Offline
Esse é provavelmente o caso de uso mais comum — e com razão. Sincronizar dados armazenados localmente com o servidor quando o app tá em segundo plano é extremamente útil pra apps que precisam funcionar offline:
import AsyncStorage from '@react-native-async-storage/async-storage';
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
const TAREFA_SYNC_OFFLINE = 'sync-dados-offline';
TaskManager.defineTask(TAREFA_SYNC_OFFLINE, async () => {
try {
// Buscar dados pendentes no armazenamento local
const dadosPendentes = await AsyncStorage.getItem('pendingSync');
if (!dadosPendentes) {
return BackgroundTask.BackgroundTaskResult.Success;
}
const itens = JSON.parse(dadosPendentes);
// Enviar dados ao servidor
const response = await fetch('https://api.seuservidor.com/sync', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ itens }),
});
if (response.ok) {
// Limpar dados pendentes após sincronização
await AsyncStorage.removeItem('pendingSync');
return BackgroundTask.BackgroundTaskResult.Success;
}
return BackgroundTask.BackgroundTaskResult.Failed;
} catch (error) {
console.error('Erro na sincronização:', error);
return BackgroundTask.BackgroundTaskResult.Failed;
}
});
Verificação de Atualizações com expo-updates
Outro caso de uso bem interessante: verificar se existem atualizações OTA disponíveis pro app enquanto ele tá em segundo plano. Quando o usuário abrir o app da próxima vez, a versão mais recente já vai estar lá:
import * as BackgroundTask from 'expo-background-task';
import * as TaskManager from 'expo-task-manager';
import * as Updates from 'expo-updates';
const TAREFA_VERIFICAR_UPDATES = 'verificar-atualizacoes';
TaskManager.defineTask(TAREFA_VERIFICAR_UPDATES, async () => {
try {
const update = await Updates.checkForUpdateAsync();
if (update.isAvailable) {
await Updates.fetchUpdateAsync();
// A atualização será aplicada na próxima abertura do app
}
return BackgroundTask.BackgroundTaskResult.Success;
} catch (error) {
console.error('Erro ao verificar atualizações:', error);
return BackgroundTask.BackgroundTaskResult.Failed;
}
});
Boas Práticas e Otimizações para 2026
O iOS 18+ e o Android 15 trouxeram lógicas bem agressivas de otimização de bateria. Se você não tomar cuidado, o sistema pode marcar seu app como "consumidor excessivo de energia" e restringir as tarefas permanentemente. Já vi isso acontecer em produção e não é nada divertido.
Mantenha as Tarefas Leves
- Faça apenas o trabalho mínimo necessário em cada execução
- Use endpoints de API que retornem apenas deltas (mudanças incrementais)
- Evite processamento pesado como manipulação de imagens ou cálculos intensivos
- Termine a tarefa o mais rápido possível — quanto mais curta, melhor
Respeite os Limites do Sistema
- Não tente contornar as restrições do SO — o sistema vai perceber e punir seu app
- Defina intervalos razoáveis — nem sempre 15 minutos é necessário pra sua use case
- Teste em dispositivos reais com bateria limitada
- No iOS, considere que as tarefas podem rodar apenas uma ou duas vezes por dia
Gerencie Erros Adequadamente
- Sempre retorne
BackgroundTaskResult.SuccessouBackgroundTaskResult.Failed - Use blocos try/catch — exceções não tratadas em tarefas de background podem causar comportamento imprevisível
- Implemente retry com backoff exponencial pra falhas de rede
- No iOS, use o
addExpirationListenerpra salvar progresso parcial quando o sistema encerrar a tarefa
Erros Comuns e Soluções
Tarefa não é executada
Problema: A tarefa foi registrada mas nunca roda.
Soluções:
- Verifique se o
defineTaskestá no escopo global (fora de componentes) - No iOS, execute
npx expo prebuildpra aplicar as configurações noInfo.plist - Confirme que o dispositivo tem conexão de rede ativa
- No iOS, verifique se "Atualização em segundo plano" está habilitada em Ajustes > Geral
Erro "No task request with identifier"
Problema: O Xcode mostra esse erro ao disparar a tarefa.
Solução: Execute npx expo prebuild --clean pra regenerar os arquivos nativos com as configurações corretas. Isso resolve na grande maioria dos casos.
Tarefa funciona no Android mas não no iOS
Problema: Tudo funciona perfeitamente no Android mas a tarefa nunca é acionada no iOS.
Solução: Não entre em pânico — provavelmente você está testando no simulador. A API de Background Tasks do iOS simplesmente não funciona em simuladores. Teste em um dispositivo físico e use triggerTaskWorkerForTestingAsync pra validar a lógica.
Perguntas Frequentes (FAQ)
Qual a diferença entre expo-background-task e expo-background-fetch?
O expo-background-task é o substituto moderno do expo-background-fetch. Usa APIs nativas mais recentes — WorkManager no Android e BGTaskScheduler no iOS — oferecendo mais confiabilidade e melhor gerenciamento de energia. O expo-background-fetch foi oficialmente depreciado e não vai receber atualizações a partir do Expo SDK 54.
Posso executar tarefas quando o app está completamente fechado?
Depende da plataforma. No iOS, se o usuário forçar o encerramento (swipe up), as tarefas param e não voltam até o app ser aberto novamente. No Android, as tarefas gerenciadas pelo WorkManager podem sobreviver ao encerramento do app e até reinicializações do dispositivo (dependendo das configurações de economia de bateria do fabricante).
Qual é o intervalo mínimo entre execuções?
O intervalo mínimo configurável é de 15 minutos. No Android, o WorkManager respeita esse intervalo de forma mais previsível. No iOS, o BGTaskScheduler trata isso como uma sugestão e pode agendar a tarefa pra bem depois — às vezes executando só durante a madrugada quando o dispositivo tá carregando.
Preciso de um development build pra testar?
No iOS, sim — tarefas em segundo plano não funcionam no simulador e exigem um dispositivo físico com development build. No Android, dá pra testar em emuladores tranquilamente. Em ambas as plataformas, o triggerTaskWorkerForTestingAsync permite disparar tarefas manualmente durante o desenvolvimento sem esperar pelo agendamento do sistema.
Como o SO decide quando executar minha tarefa?
O sistema considera vários fatores: nível de bateria, disponibilidade de rede, padrões de uso do app, carga do sistema e se o dispositivo está na tomada. No iOS, a Apple usa machine learning pra prever quando o usuário vai abrir o app e executa tarefas estrategicamente antes desse momento. No Android, o WorkManager usa um sistema de constraints mais direto e previsível.