
O aplicativo iFunny em que estamos trabalhando está disponível nas lojas há mais de cinco anos. Durante esse período, a equipe móvel teve que passar por muitas abordagens e migrações diferentes entre ferramentas, e há um ano houve um tempo para mudar de uma solução auto-escrita e olhar na direção de algo mais "moderno" e generalizado. Este artigo é um pequeno detalhe sobre o que foi estudado, quais soluções foram analisadas e com o que elas foram finalizadas.
Por que precisamos de tudo isso?Vamos decidir imediatamente em homenagem ao que é este artigo e por que esse tópico acabou sendo importante para a equipe de desenvolvimento do Android:
- Existem muitos cenários em que você precisa executar tarefas fora da estrutura da interface do usuário ativa;
- o sistema impõe um grande número de restrições ao lançamento de tais tarefas;
- Foi bastante difícil escolher entre as soluções existentes, pois cada ferramenta tem seus prós e contras.
Cronologia do desenvolvimento de eventos
Android 0
AlarmManager, Manipulador, Serviço
Inicialmente, suas soluções foram implementadas para iniciar tarefas baseadas em segundo plano com base em serviços. Havia também um mecanismo que vinculava tarefas ao ciclo de vida e podia cancelá-las e restaurá-las. Isso serviu à equipe por um longo tempo, já que a plataforma não impôs restrições a essas tarefas.
O Google aconselhou fazer isso com base no seguinte diagrama:

No final de 2018, não faz sentido entender isso, basta avaliar a escala do desastre.
De fato, ninguém se importava com quanto trabalho está acontecendo em segundo plano. Os aplicativos fizeram o que queriam e quando queriam.
Prós :
disponível em qualquer lugar;
acessível a todos.
Contras :
o sistema restringe o trabalho de todas as formas;
sem lançamentos por condição;
A API é mínima e você precisa escrever muito código.
Android 5. Pirulito
Jobcheduler
Após 5 (!) Anos, mais perto de 2015, o Google percebeu que as tarefas são iniciadas de maneira ineficiente. Os usuários começaram a reclamar regularmente que seus telefones estavam acabando simplesmente deitados em uma mesa ou no bolso.
Com o lançamento do Android 5, uma ferramenta como o JobScheduler apareceu. Este é um mecanismo com cuja ajuda é possível realizar vários trabalhos em segundo plano, cujo início foi otimizado e simplificado devido ao sistema centralizado para iniciar essas tarefas e à capacidade de definir condições para esse mesmo lançamento.
No código, tudo isso parece bastante simples: um serviço é anunciado no qual os eventos de início e fim ocorrem.
Das nuances: se você deseja executar um trabalho de forma assíncrona, em onStartJob, é necessário iniciar o fluxo; o principal é não esquecer de chamar o método jobFinished no final do trabalho, caso contrário, o sistema não liberará o WakeLock, sua tarefa não será considerada concluída e será perdida.
public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
De qualquer lugar do aplicativo, você pode iniciar este trabalho. As tarefas são executadas em nosso processo, mas são iniciadas no nível do IPC. Existe um mecanismo centralizado que controla sua execução e ativa o aplicativo apenas nos momentos necessários para isso. Você também pode definir várias condições de disparo e transferir dados através do Bundle.
JobInfo task = new JobInfo.Builder(JOB_ID, serviceName) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .build(); JobScheduler scheduler = (JobScheduler) context.getSystemService(JOB_SCHEDULER_SERVICE); scheduler.schedule(task);
Em geral, comparado a nada, isso já era algo. Mas esse mecanismo está disponível apenas na API 21 e, no momento do lançamento do Android 5.0, seria estranho parar de oferecer suporte a todos os dispositivos antigos (três anos se passaram e ainda oferecemos suporte a quatro).
Prós :
A API é simples;
condições para o lançamento.
Contras :
Disponível a partir da API 21de fato, apenas com API 23;
fácil cometer um erro.
Android 5. Pirulito
Gerenciador de rede Gcm
Também foi apresentado um análogo do JobScheduler - GCM Network Manager. Essa é uma biblioteca que fornece funcionalidade semelhante, mas já trabalhava com a API 9. É verdade que exigia o Google Play Services em troca. Aparentemente, a funcionalidade necessária para o JobScheduler funcionar começou a ser entregue não apenas na versão Android, mas também no nível do GPS. Note-se que os desenvolvedores da estrutura mudaram de idéia muito rapidamente e decidiram não conectar seu futuro ao GPS. Obrigado a eles por isso.
Tudo parece absolutamente idêntico. Mesmo serviço:
public class GcmNetworkManagerService extends GcmTaskService { @Override public int onRunTask(TaskParams taskParams) { doWork(taskParams); return 0; } }
A mesma tarefa é iniciada:
OneoffTask task = new OneoffTask.Builder() .setService(GcmNetworkManagerService.class) .setTag(TAG) .setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED) .setRequiresCharging(true) .build(); GcmNetworkManager mGcmNetworkManager = GcmNetworkManager.getInstance(this); mGcmNetworkManager.schedule(task);
Essa semelhança de arquitetura foi ditada pela funcionalidade herdada e pelo desejo de obter uma migração simples entre ferramentas.
Prós :
API semelhante ao JobScheduler;
Disponível a partir da API 9.
Contras :
Você deve ter o Google Play Services
fácil cometer um erro.Android 5. Pirulito
WakefulBroadcastReceiver
A seguir, escreverei algumas palavras sobre um dos mecanismos básicos usados no JobScheduler e disponíveis diretamente aos desenvolvedores. Este é o WakeLock e seu WakefulBroadcastReceiver baseado.
Usando o WakeLock, você pode impedir que o sistema fique em suspensão, ou seja, mantenha o dispositivo em um estado ativo. Isso é necessário se queremos fazer algum trabalho importante.
Ao criar o WakeLock, você pode especificar suas configurações: mantenha a CPU, a tela ou o teclado.
PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE) PowerManager.WakeLock wl = pm.newWakeLock(PARTIAL_WAKE_LOCK, "name") wl.acquire(timeout);
Com base nesse mecanismo, o WakefulBroadcastReceiver funciona. Iniciamos o serviço e mantemos o WakeLock.
public class SimpleWakefulReceiver extends WakefulBroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { Intent service = new Intent(context, SimpleWakefulService.class); startWakefulService(context, service); } }
Após o serviço concluir o trabalho necessário, o liberamos por métodos semelhantes.
Em 4 versões, este BroadcastReceiver ficará obsoleto e as seguintes alternativas serão descritas em developer.android.com:
- JobScheduler;
- Syncadapter
- DownloadManager
- FLAG_KEEP_SCREEN_ON para janela.
Android 6. Marshmallow
DozeMode: Durma em movimento
Em seguida, o Google começou a aplicar várias otimizações para aplicativos em execução no dispositivo. Mas o que é otimização para o usuário é uma limitação para o desenvolvedor.
O primeiro passo foi o DozeMode, que coloca o dispositivo no modo de suspensão se ficar inativo por um certo tempo. Nas primeiras versões, durou uma hora; nas versões subsequentes, a duração do sono foi reduzida para 30 minutos. Periodicamente, o telefone acorda, executa todas as tarefas pendentes e adormece novamente. A janela DozeMode se expande exponencialmente. Todas as transições entre modos podem ser rastreadas pelo adb.
Quando o DozeMode ocorre, as seguintes restrições são impostas ao aplicativo:
- o sistema ignora todos os WakeLock;
- AlarmManager está atrasado;
- JobScheduler não funciona;
- SyncAdapter não funciona;
- o acesso à rede é limitado.
Você também pode adicionar seu aplicativo à lista de permissões, para que não caia nas limitações do DozeMode, mas pelo menos a Samsung ignorou completamente esta lista.
Android 6. Marshmallow
AppStandby: aplicativos inativos
O sistema identifica aplicativos inativos e impõe a eles as mesmas restrições que no DozeMode.
Um aplicativo é enviado para isolamento se:
- não possui um processo em primeiro plano;
- não possui uma notificação ativa;
- não adicionado à lista de exclusão.
Android 7. Nougat
Otimizações em segundo plano. Svelte
Svelte é um projeto no qual o Google está tentando otimizar o consumo de RAM por aplicativos e pelo próprio sistema.
No Android 7, no âmbito deste projeto, foi decidido que as transmissões implícitas não são muito eficazes, pois são ouvidas por um grande número de aplicativos e o sistema gasta uma grande quantidade de recursos quando esses eventos ocorrem. Portanto, os seguintes tipos de eventos foram proibidos para declaração no manifesto:
- CONNECTIVITY_ACTION;
- ACTION_NEW_PICTURE;
- ACTION_NEW_VIDEO.
Android 7. Nougat
FirebaseJobDispatcher
Ao mesmo tempo, uma nova versão da estrutura de inicialização de tarefas foi publicada - FirebaseJobDispatcher. De fato, foi o GCM NetworkManager concluído, que foi arrumado um pouco e tornado um pouco mais flexível.
Visualmente, tudo parecia exatamente o mesmo. Mesmo serviço:
public class JobSchedulerService extends JobService { @Override public boolean onStartJob(JobParameters params) { doWork(params); return false; } @Override public boolean onStopJob(JobParameters params) { return false; } }
A única diferença entre ele era a capacidade de instalar o driver. Um driver é a classe responsável pela estratégia de lançamento da tarefa.
O lançamento das tarefas em si não mudou ao longo do tempo.
FirebaseJobDispatcher dispatcher = new FirebaseJobDispatcher(new GooglePlayDriver(context)); Job task = dispatcher.newJobBuilder() .setService(FirebaseJobDispatcherService.class) .setTag(TAG) .setConstraints(Constraint.ON_UNMETERED_NETWORK, Constraint.DEVICE_IDLE) .build(); dispatcher.mustSchedule(task);
Prós :
API semelhante ao JobScheduler;
Disponível a partir da API 9.
Contras :
Você deve ter o Google Play Services
fácil cometer um erro.
Foi encorajador instalar meu driver para me livrar do GPS. Até pesquisamos, mas finalmente encontramos o seguinte:


O Google sabe disso, mas essas tarefas permanecem abertas por vários anos.
Android 7. Nougat
Android Job por Evernote
Como resultado, a comunidade não aguentou e uma solução criada por você surgiu na forma de uma biblioteca do Evernote. Não foi o único, mas foi a solução do Evernote que conseguiu se estabelecer e "entrar no povo".
Em termos de arquitetura, essa biblioteca era mais conveniente que seus antecessores.
A entidade responsável pela criação de tarefas apareceu. No caso do JobScheduler, eles foram criados através da reflexão.
class SendLogsJobCreator : JobCreator { override fun create(tag: String): Job? { when (tag) { SendLogsJob.TAG -> return SendLogsJob() } return null } }
Há uma classe separada, que é a própria tarefa. No JobScheduler, tudo isso foi despejado em um comutador dentro do StartJob.
class SendLogsJob : Job() { override fun onRunJob(params: Params): Result { return doWork(params) } }
O lançamento de tarefas é idêntico, mas além dos eventos herdados, o Evernote também adicionou seus próprios, como o lançamento de tarefas diárias, tarefas exclusivas e inicialização dentro da janela.
new JobRequest.Builder(JOB_ID) .setRequiresDeviceIdle(true) .setRequiresCharging(true) .setRequiredNetworkType(JobRequest.NetworkType.UNMETERED) .build() .scheduleAsync();
Prós :
API conveniente;
suportado em todas as versões;
Não precisa do Google Play Services.
Contras :
solução de terceiros.
Os caras apoiaram ativamente sua biblioteca. Embora existissem alguns problemas críticos, ele funcionou em todas as versões e em todos os dispositivos. Como resultado, no ano passado, nossa equipe do Android escolheu uma solução do Evernote, já que as bibliotecas do Google cortaram uma grande camada de dispositivos aos quais eles não podem suportar.
Por dentro, ela trabalhou em soluções do Google, em casos extremos - com o AlarmManager.
Android 8. Oreo
Limites de execução em segundo plano
Vamos voltar às nossas limitações. Com o advento do novo Android, novas otimizações chegaram. Os caras do Google encontraram outro problema. Desta vez, tudo se transformou em serviços e transmissões (sim, nada de novo).
startService se aplicativos em segundo planotransmissão implícita no manifesto
Em primeiro lugar, foi proibido iniciar serviços em segundo plano. No "quadro da lei" permaneceu apenas os serviços em primeiro plano. Os serviços agora podem ser considerados obsoletos.
A segunda limitação é a mesma transmissão. Dessa vez, foi proibido registrar TODAS as transmissões implícitas no manifesto. Transmissão implícita é uma transmissão, que se destina não apenas ao nosso aplicativo. Por exemplo, há a Ação ACTION_PACKAGE_REPLACED e há ACTION_MY_PACKAGE_REPLACED. Então, o primeiro está implícito.
Mas qualquer transmissão ainda pode ser registrada através do Context.registerBroadcast.
Android 9. Torta
Workmanager
Sobre esta otimização parou ainda. Talvez os dispositivos tenham começado a trabalhar com rapidez e cuidado em termos de consumo de energia; talvez os usuários tenham reclamado menos sobre isso.
No Android 9, os desenvolvedores da estrutura abordaram completamente a ferramenta para iniciar tarefas. Na tentativa de resolver todos os problemas prementes, foi introduzida uma biblioteca no Google I / O para iniciar as tarefas em segundo plano do WorkManager.
O Google recentemente tentou moldar sua visão da arquitetura do aplicativo Android e oferece aos desenvolvedores as ferramentas necessárias para isso. Portanto, havia componentes de arquitetura com LiveData, ViewModel e Room. O WorkManager parece um complemento razoável para sua abordagem e paradigma.
Se falamos sobre como o WorkManager é organizado no interior, não há avanço tecnológico nele. De fato, este é um invólucro de soluções existentes: JobScheduler, FirebaseJobDispatcher e AlarmManager.
createBestAvailableBackgroundScheduler static Scheduler createBestAvailableBackgroundScheduler(Context, WorkManager) { if (Build.VERSION.SDK_INT >= MIN_JOB_SCHEDULER_API_LEVEL) { return new SystemJobScheduler(context, workManager); } try { return tryCreateFirebaseJobScheduler(context); } catch (Exception e) { return new SystemAlarmScheduler(context); } }
O código de seleção é bem simples. Mas deve-se notar que o JobScheduler está disponível a partir da API 21, mas eles o usam apenas na API 23, pois as primeiras versões eram bastante instáveis.
Se a versão for inferior a 23, então, através da reflexão, tentamos encontrar o FirebaseJobDispatcher, caso contrário, usamos o AlarmManager.
Vale a pena notar que o invólucro saiu bastante flexível. Desta vez, os desenvolvedores dividiram tudo em entidades separadas e, arquitetonicamente, parece conveniente:
- Trabalhador - lógica de trabalho;
- WorkRequest - lógica de lançamento de tarefas;
- WorkRequest.Builder - parâmetros;
- Restrições - condições;
- WorkManager - um gerente que gerencia tarefas;
- WorkStatus - status da tarefa.

As condições de inicialização foram herdadas do JobScheduler.
Pode-se observar que o gatilho para alterar o URI apareceu apenas com a API 23. Além disso, você pode assinar a alteração não apenas de um URI específico, mas também de todos os aninhados usando o sinalizador no método
Se falamos sobre nós, então, no estágio alfa, foi decidido mudar para o WorkManager.
Existem várias razões para isso. O Evernote possui alguns bugs críticos que os desenvolvedores da biblioteca prometem corrigir com a transição para uma versão com o WorkManager integrado. E eles mesmos concordam que a decisão do Google nega as vantagens do Evernote. Além disso, esta solução se adapta bem à nossa arquitetura, pois usamos componentes de arquitetura.
Além disso, gostaria de mostrar com um exemplo simples como estamos tentando usar essa abordagem. Ao mesmo tempo, não é muito crítico se você possui um WorkManager ou JobScheduler.


Vejamos um exemplo com um caso muito simples: clicar em republicar ou gostar.
Agora, todos os aplicativos estão tentando evitar solicitações de bloqueio de rede, pois isso deixa o usuário nervoso e o faz esperar, embora, nesse momento, ele possa fazer compras dentro do aplicativo ou assistir a anúncios.
Nesses casos, os dados locais são alterados primeiro - o usuário vê imediatamente o resultado de sua ação. Em segundo plano, há uma solicitação ao servidor; se isso falhar, os dados serão redefinidos para seu estado inicial.
A seguir, mostrarei um exemplo de como fica conosco.
O JobRunner contém a lógica para iniciar tarefas. Seus métodos descrevem a configuração de tarefas e passam parâmetros.
JobRunner.java fun likePost(content: IFunnyContent) { val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .build() val input = Data.Builder() .putString(LikeContentJob.ID, content.id) .build() val request = OneTimeWorkRequest.Builder(LikeContentJob::class.java) .setInputData(input) .setConstraints(constraints) .build() WorkManager.getInstance().enqueue(request) }
A tarefa em si no WorkManager é a seguinte: pegamos o ID dos parâmetros e chamamos o método no servidor para gostar desse conteúdo.
Temos uma classe base que contém a seguinte lógica:
abstract class BaseJob : Worker() { final override fun doWork(): Result { val workerInjector = WorkerInjectorProvider.injector() workerInjector.inject(this) return performJob(inputData) } abstract fun performJob(params: Data): Result }
Primeiro, ele permite que você se afaste um pouco do conhecimento explícito do Worker. Ele também contém a lógica de injeção de dependência por meio do WorkerInjector.
WorkerInjectorImpl.java @Singleton public class WorkerInjectorImpl implements WorkerInjector { @Inject public WorkerInjectorImpl() {} @Ovierride public void inject(Worker job) { if (worker instanceof AppCrashedEventSendJob) { Injector.getAppComponent().inject((AppCrashedEventSendJob) job); } else if (worker instanceof CheckNativeCrashesJob) { Injector.getAppComponent().inject((CheckNativeCrashesJob) job); } } }
Simplesmente envia proxies para Dagger, mas nos ajuda a testar: substituímos as implementações de injetores e implementamos o ambiente necessário nas tarefas.
fun void testRegisterPushProvider() { WorkManagerTestInitHelper.initializeTestWorkManager(context) val testDriver = WorkManagerTestInitHelper.getTestDriver() WorkerInjectorProvider.setInjector(TestInjector())
class LikePostInteractor @Inject constructor( val iFunnyContentDao: IFunnyContentDao, val jobRunner: JobRunner) : Interactor { fun execute() { iFunnyContentDao.like(getContent().id) jobRunner.likePost(getContent()) } }
Interactor é a entidade que o ViewController puxa para iniciar a passagem do script (neste caso, como ele). Marcamos o conteúdo localmente como "carregado" e enviamos a tarefa para execução. Se a tarefa falhar, o semelhante será removido.
class IFunnyContentViewModel(val iFunnyContentDao: IFunnyContentDao) : ViewModel() { val likeState = MediatorLiveData<Boolean>() var iFunnyContentId = MutableLiveData<String>() private var iFunnyContentState: LiveData<IFunnyContent> = attachLiveDataToContentId(); init { likeState.addSource(iFunnyContentState) { likeState.postValue(it!!.hasLike) } } }
Usamos os componentes de arquitetura do Google: ViewModel e LiveData. É assim que o nosso ViewModel se parece. Aqui, conectamos a atualização do objeto no DAO ao status de like.
IFunnyContentViewController.java class IFunnyContentViewController @Inject constructor( private val likePostInteractor: LikePostInteractor, val viewModel: IFunnyContentViewModel) : ViewController { override fun attach(view: View) { viewModel.likeState.observe(lifecycleOwner, { updateLikeView(it!!) }) } fun onLikePost() { likePostInteractor.setContent(getContent()) likePostInteractor.execute() } }
O ViewController, por um lado, assina a alteração do status do semelhante, por outro lado, inicia a passagem do script que precisamos.
E isso é praticamente todo o código que precisamos. Resta acrescentar o comportamento do próprio View com o gosto e a implementação do seu DAO; se você usar Sala, basta registrar os campos no objeto. Parece bem simples e eficaz.
Resumir
JobScheduler, GCM Network Manager, FirebaseJobDispatcher:- não os use
- não leia mais artigos sobre eles
- não assista relatórios
- não pense qual escolher.
Android Job by Evernote:- Dentro eles usarão o WorkManager;
- erros críticos são borrados entre as soluções.
WorkManager:- API NÍVEL 9+;
- independente do Google Play Services;
- Encadeamento / InputMergers;
- abordagem reativa;
- suporte do Google (eu quero acreditar).