Pegue um café e se ajeite na cadeira, pois vou contar uma história um pouco longa a respeito de como executar serviços em segundo plano com React-Native e imagino que vai servir mais como um norte na hora de desenvolver esse tipo de funcionalidade.
Primeiro, pesquisei na documentação oficial do React, e lá é indicado um recurso chamado Headless JS, que permite criar uma ponte entre o JS e o código nativo, e executar tarefas em background.
Em seguida, fiz uma pesquisa sobre como funcionam as tarefas em segundo plano no Android para entender quais são as possibilidades e prós e contras de cada abordagem. Sobre as abordagens, temos:
# WorkManager
A API WorkManager facilita a programação de tarefas adiáveis e assíncronas que precisam ser executadas mesmo se o app fechar ou o dispositivo reiniciar.
Principais recursos:
O WorkManager é destinado a tarefas que sejam adiáveis, ou seja, que não precisem ser executadas imediatamente, e que precisem ser executadas de forma confiável, mesmo que o app feche ou o dispositivo reinicie. Por exemplo: Envio de registros ou análises para serviços de back-end; ou sincronização periódica de dados do app com um servidor.
O WorkManager não se destina ao trabalho em segundo plano em andamento que possa ser encerrado com segurança se o processo do app é encerrado ou para tarefas que exijam execução imediata.
# Serviços de primeiro plano
Para o trabalho iniciado pelo usuário que precisa ser executado imediatamente até o fim, use um serviço em primeiro plano. Usar um serviço em primeiro plano informa ao sistema que o app está fazendo algo importante que não pode ser eliminado. Os serviços em primeiro plano ficam visíveis para os usuários por meio de uma notificação não dispensável na bandeja de notificações.
# AlarmManager
Se você precisar executar um job em um horário específico, use o AlarmManager. O AlarmManager inicia seu app, se necessário, para fazer o job no horário especificado. No entanto, se o job não recisar ser executado em um horário específico, o WorkManager é uma opção melhor. O WorkManager consegue equilibrar melhor os recursos do sistema. Por exemplo, se você precisar executar um job a cada hora, mas não precisar que ele seja executado em um horário específico, use o WorkManager para configurar o job recorrente.
# DownloadManager
Se o app estiver realizando downloads HTTP de longa duração, use o DownloadManager. Os clientes podem solicitar o download de um URI para um arquivo de destino específico que pode estar fora do processo do app. O Gerenciador de downloads realizará o download em segundo plano, cuidando das interações HTTP e tentando refazer os downloads após falhas ou em todas as mudanças de conectividade e reinicializações do sistema.
Diante de todas essas informações, estudei os cenários de execução do meu problema e com base nas regras de negócio, a melhor opção foi criar um serviço e executá-lo em primeiro plano, isso garantiria que as diretrizes que precisavam ser verificadas constantemente seriam devidamente atendidas. De qualquer forma, você pode combinar o uso dos recursos como lhe for conveniente, tendo em vista que podem existir cenários diferentes dentro de uma mesma aplicação.
Para termos uma referência da implementação apresento como os serviços foram implementados.
# Estrutura
Dentro de '~/android/app/src/main/java/com/nome_do_projeto', criei os arquivos:
- modules
|-RoutinesModule.java
- packages
|-RoutinesPackage.java
- receivers
|-BootupReceiver.java
- services
|-RoutineService.java
|-RoutinesServiceRunner.java
Não vou entrar em detalhes de implementação, mas de um modo geral:
-------------------
-------------------
E inseri a referência dos serviços e do receiver implementados:
-------------------
-------------------
E por último, inseri o load do nosso package no arquivo 'MainApplication.java':
-------------------
-------------------
Agora você já conseguirá acessar a interface do módulo.
# Como usar?
Primeiro, é necessário registrar a tarefa no contexto de execução do app. Pra isso, é só adicionar usando registerHeadlessTask, no arquivo './index.js' da sua aplicação. Veja:
-------------------
-------------------
Observe que abstraí o código que será executado dentro do domínio de '~/tasks'. Veja:
-------------------
-------------------
Perceba que a função headlessTask é de fato o código que será executado. Para iniciar o serviço, é só importar essa task e usá-la, veja:
-------------------
//... depois use como desejar, dessa forma
Deixei esse guia aqui pra servir de orientação inicial, mas recomendo fortemente ler as documentações oficiais das tecnologias envolvidas e analisar sempre o cenário em que vai utilizar, para fazer uma escolha mais adequada. Criei um gist com as implementações que você pode conferir aqui.
Primeiro, pesquisei na documentação oficial do React, e lá é indicado um recurso chamado Headless JS, que permite criar uma ponte entre o JS e o código nativo, e executar tarefas em background.
Em seguida, fiz uma pesquisa sobre como funcionam as tarefas em segundo plano no Android para entender quais são as possibilidades e prós e contras de cada abordagem. Sobre as abordagens, temos:
# WorkManager
A API WorkManager facilita a programação de tarefas adiáveis e assíncronas que precisam ser executadas mesmo se o app fechar ou o dispositivo reiniciar.
Principais recursos:
- Compatível com versões anteriores até a API 14
- Usa o JobScheduler em dispositivos com a API 23 ou posteriores
- Usa uma combinação de BroadcastReceiver + AlarmManager em dispositivos com API 14 a 22
- Adiciona restrições de trabalho, como disponibilidade de rede ou status de carregamento
- Programa tarefas pontuais ou periódicas assíncronas
- Monitora e gerencia tarefas programadas
- Encadeia tarefas
- Garante a execução de tarefas, mesmo que o app ou dispositivo seja reiniciado
- Tem recursos de economia de energia, como o modo "Soneca"
O WorkManager é destinado a tarefas que sejam adiáveis, ou seja, que não precisem ser executadas imediatamente, e que precisem ser executadas de forma confiável, mesmo que o app feche ou o dispositivo reinicie. Por exemplo: Envio de registros ou análises para serviços de back-end; ou sincronização periódica de dados do app com um servidor.
O WorkManager não se destina ao trabalho em segundo plano em andamento que possa ser encerrado com segurança se o processo do app é encerrado ou para tarefas que exijam execução imediata.
# Serviços de primeiro plano
Para o trabalho iniciado pelo usuário que precisa ser executado imediatamente até o fim, use um serviço em primeiro plano. Usar um serviço em primeiro plano informa ao sistema que o app está fazendo algo importante que não pode ser eliminado. Os serviços em primeiro plano ficam visíveis para os usuários por meio de uma notificação não dispensável na bandeja de notificações.
# AlarmManager
Se você precisar executar um job em um horário específico, use o AlarmManager. O AlarmManager inicia seu app, se necessário, para fazer o job no horário especificado. No entanto, se o job não recisar ser executado em um horário específico, o WorkManager é uma opção melhor. O WorkManager consegue equilibrar melhor os recursos do sistema. Por exemplo, se você precisar executar um job a cada hora, mas não precisar que ele seja executado em um horário específico, use o WorkManager para configurar o job recorrente.
# DownloadManager
Se o app estiver realizando downloads HTTP de longa duração, use o DownloadManager. Os clientes podem solicitar o download de um URI para um arquivo de destino específico que pode estar fora do processo do app. O Gerenciador de downloads realizará o download em segundo plano, cuidando das interações HTTP e tentando refazer os downloads após falhas ou em todas as mudanças de conectividade e reinicializações do sistema.
Diante de todas essas informações, estudei os cenários de execução do meu problema e com base nas regras de negócio, a melhor opção foi criar um serviço e executá-lo em primeiro plano, isso garantiria que as diretrizes que precisavam ser verificadas constantemente seriam devidamente atendidas. De qualquer forma, você pode combinar o uso dos recursos como lhe for conveniente, tendo em vista que podem existir cenários diferentes dentro de uma mesma aplicação.
Para termos uma referência da implementação apresento como os serviços foram implementados.
# Estrutura
Dentro de '~/android/app/src/main/java/com/nome_do_projeto', criei os arquivos:
- modules
|-RoutinesModule.java
- packages
|-RoutinesPackage.java
- receivers
|-BootupReceiver.java
- services
|-RoutineService.java
|-RoutinesServiceRunner.java
Não vou entrar em detalhes de implementação, mas de um modo geral:
- RoutinesModule - É a implementação da API de recursos nativos para o React e basicamente serve de interface para executar os serviços que desejamos;
- RoutinesPackage - É o arquivo que empacota nosso módulo;
- BootupReceiver - Implementa um BroadcastReceiver que no boot do SO inicia o processo;
- RoutineService - É uma interface que vai permitir o bind o código que desenvolvermos em JS ser executado dentro do contexto do serviço;
- RoutinesServiceRunner - Ele não precisaria, contudo, criei para gerenciar um handler que permitiria o serviço ser restartado dentro de um ciclo temporal.
-------------------
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
E inseri a referência dos serviços e do receiver implementados:
-------------------
<service
android:name="com.example.services.RoutinesServiceRunner"
android:enabled="true"
android:exported="false" />
<service android:name="com.example.services.RoutinesService" />
<receiver
android:name="com.example.receivers.BootupReceiver"
android:enabled="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
E por último, inseri o load do nosso package no arquivo 'MainApplication.java':
-------------------
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new RoutinesPackage());
return packages;
}
Agora você já conseguirá acessar a interface do módulo.
# Como usar?
Primeiro, é necessário registrar a tarefa no contexto de execução do app. Pra isso, é só adicionar usando registerHeadlessTask, no arquivo './index.js' da sua aplicação. Veja:
-------------------
mport {AppRegistry} from 'react-native';
import App from './src';
import {name as appName} from './app.json';
import {headlessTask} from '~/tasks/BackgroundRoutines';
AppRegistry.registerHeadlessTask('BackgroundRoutines', () => headlessTask);
AppRegistry.registerComponent(appName, () => App);
Observe que abstraí o código que será executado dentro do domínio de '~/tasks'. Veja:
-------------------
import {NativeModules} from 'react-native';
const {BackgroundRoutines} = NativeModules;
const headlessTask = async () => {
const time = new Date().toLocaleTimeString('pt-BR', {
timeZone: 'America/Sao_Paulo',
});
const status = `Executou a tarefa às ${time}`;
console.log('BACKGROUND > ', status);
};
export {BackgroundRoutines, headlessTask};
Perceba que a função headlessTask é de fato o código que será executado. Para iniciar o serviço, é só importar essa task e usá-la, veja:
-------------------
import {NativeModules} from 'react-native';
import BackgroundRoutines from '~/tasks/BackgroundRoutines';
BackgroundRoutines.start();
-------------------Deixei esse guia aqui pra servir de orientação inicial, mas recomendo fortemente ler as documentações oficiais das tecnologias envolvidas e analisar sempre o cenário em que vai utilizar, para fazer uma escolha mais adequada. Criei um gist com as implementações que você pode conferir aqui.
Comentários
Postar um comentário