Threading Android: tudo o que você precisa saber
Publicados: 2022-03-11Todo desenvolvedor Android, em um ponto ou outro, precisa lidar com threads em seu aplicativo.
Quando um aplicativo é iniciado no Android, ele cria o primeiro thread de execução, conhecido como thread “principal”. O encadeamento principal é responsável por enviar eventos para os widgets de interface de usuário apropriados, bem como comunicar-se com os componentes do kit de ferramentas da interface do usuário do Android.
Para manter seu aplicativo responsivo, é essencial evitar o uso do thread principal para realizar qualquer operação que possa acabar mantendo-o bloqueado.
Operações de rede e chamadas de banco de dados, bem como o carregamento de determinados componentes, são exemplos comuns de operações que devem ser evitadas na thread principal. Quando eles são chamados no thread principal, eles são chamados de forma síncrona, o que significa que a interface do usuário permanecerá completamente sem resposta até que a operação seja concluída. Por esse motivo, eles geralmente são executados em threads separados, o que evita o bloqueio da interface do usuário enquanto estão sendo executados (ou seja, são executados de forma assíncrona a partir da interface do usuário).
O Android oferece muitas maneiras de criar e gerenciar threads, e existem muitas bibliotecas de terceiros que tornam o gerenciamento de threads muito mais agradável. No entanto, com tantas abordagens diferentes à mão, escolher o caminho certo pode ser bastante confuso.
Neste artigo, você conhecerá alguns cenários comuns no desenvolvimento Android em que o encadeamento se torna essencial e algumas soluções simples que podem ser aplicadas a esses cenários e muito mais.
Encadeamento no Android
No Android, você pode categorizar todos os componentes de segmentação em duas categorias básicas:
- Threads que estão anexados a uma atividade/fragmento: Esses threads estão vinculados ao ciclo de vida da atividade/fragmento e são encerrados assim que a atividade/fragmento é destruída.
- Threads que não estão anexados a nenhuma atividade/fragmento: esses threads podem continuar a ser executados além do tempo de vida da atividade/fragmento (se houver) a partir do qual foram gerados.
Componentes de encadeamento que se anexam a uma atividade/fragmento
AsyncTask
AsyncTask
é o componente Android mais básico para encadeamento. É simples de usar e pode ser bom para cenários básicos.
Exemplo de uso:
public class ExampleActivity extends Activity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyTask().execute(url); } private class MyTask extends AsyncTask<String, Void, String> { @Override protected String doInBackground(String... params) { String url = params[0]; return doSomeWork(url); } @Override protected void onPostExecute(String result) { super.onPostExecute(result); // do something with result } } }
AsyncTask
, no entanto, fica aquém se você precisar que sua tarefa adiada seja executada além do tempo de vida da atividade/fragmento. Vale ressaltar que mesmo algo tão simples como a rotação da tela pode fazer com que a atividade seja destruída.
Carregadores
Os carregadores são a solução para o problema mencionado acima. Os carregadores podem parar automaticamente quando a atividade é destruída e também podem reiniciar depois que a atividade for recriada.
Existem basicamente dois tipos de carregadores: AsyncTaskLoader
e CursorLoader
. Você aprenderá mais sobre o CursorLoader
posteriormente neste artigo.
AsyncTaskLoader
é semelhante a AsyncTask
, mas um pouco mais complicado.
Exemplo de uso:
public class ExampleActivity extends Activity{ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); getLoaderManager().initLoader(1, null, new MyLoaderCallbacks()); } private class MyLoaderCallbacks implements LoaderManager.LoaderCallbacks { @Override public Loader onCreateLoader(int id, Bundle args) { return new MyLoader(ExampleActivity.this); } @Override public void onLoadFinished(Loader loader, Object data) { } @Override public void onLoaderReset(Loader loader) { } } private class MyLoader extends AsyncTaskLoader { public MyLoader(Context context) { super(context); } @Override public Object loadInBackground() { return someWorkToDo(); } } }
Componentes de encadeamento que não são anexados a uma atividade/fragmento
Serviço
Service
é um componente útil para realizar operações longas (ou potencialmente longas) sem nenhuma interface do usuário.
Service
é executado no encadeamento principal de seu processo de hospedagem; o serviço não cria seu próprio encadeamento e não é executado em um processo separado, a menos que você especifique o contrário.
Exemplo de uso:
public class ExampleService extends Service { @Override public int onStartCommand(Intent intent, int flags, int startId) { doSomeLongProccesingWork(); stopSelf(); return START_NOT_STICKY; } @Nullable @Override public IBinder onBind(Intent intent) { return null; } }
Com Service
, é sua responsabilidade interrompê-lo quando seu trabalho estiver concluído, chamando o stopSelf()
ou stopService()
.
Serviço de intenção
Assim como Service
, IntentService
é executado em um thread separado e para automaticamente após concluir seu trabalho.
IntentService
geralmente é usado para tarefas curtas que não precisam ser anexadas a nenhuma interface do usuário.
Exemplo de uso:
public class ExampleService extends IntentService { public ExampleService() { super("ExampleService"); } @Override protected void onHandleIntent(Intent intent) { doSomeShortWork(); } }
Sete padrões de encadeamento no Android
Caso de uso nº 1: fazer uma solicitação pela rede sem exigir uma resposta do servidor
Às vezes, você pode querer enviar uma solicitação de API para um servidor sem precisar se preocupar com sua resposta. Por exemplo, você pode estar enviando um token de registro push para o back-end do seu aplicativo.
Como isso envolve fazer uma solicitação pela rede, você deve fazê-lo de um encadeamento diferente do encadeamento principal.
Opção 1: AsyncTask ou carregadores
Você pode usar AsyncTask
ou carregadores para fazer a chamada e funcionará.
No entanto, AsyncTask
e carregadores dependem do ciclo de vida da atividade. Isso significa que você precisará aguardar a execução da chamada e tentar impedir que o usuário saia da atividade ou esperar que ela seja executada antes que a atividade seja destruída.
Opção 2: Serviço
Service
pode ser mais adequado para esse caso de uso, pois não está vinculado a nenhuma atividade. Portanto, ele poderá continuar com a chamada de rede mesmo após a destruição da atividade. Além disso, como a resposta do servidor não é necessária, um serviço também não seria limitante aqui.
No entanto, como um serviço começará a ser executado no encadeamento da interface do usuário, você ainda precisará gerenciar o encadeamento. Você também precisará certificar-se de que o serviço seja interrompido assim que a chamada de rede for concluída.
Isso exigiria mais esforço do que o necessário para uma ação tão simples.
Opção 3: IntentService
Essa, na minha opinião, seria a melhor opção.
Como o IntentService
não se conecta a nenhuma atividade e é executado em um thread que não é da interface do usuário, ele atende perfeitamente às nossas necessidades aqui. Além disso, IntentService
para automaticamente, portanto, também não há necessidade de gerenciá-lo manualmente.
Caso de uso nº 2: fazer uma chamada de rede e obter a resposta do servidor
Este caso de uso é provavelmente um pouco mais comum. Por exemplo, você pode querer invocar uma API no back-end e usar sua resposta para preencher campos na tela.
Opção 1: Serviço ou IntentService
Embora um Service
ou um IntentService
se saído bem no caso de uso anterior, usá-los aqui não seria uma boa ideia. Tentar obter dados de um Service
ou IntentService
para o thread principal da interface do usuário tornaria as coisas muito complexas.
Opção 2: AsyncTask ou carregadores
À primeira vista, AsyncTask
ou carregadores parecem ser a solução óbvia aqui. Eles são fáceis de usar — simples e diretos.
No entanto, ao usar AsyncTask
ou carregadores, você notará que há a necessidade de escrever algum código clichê. Além disso, o tratamento de erros torna-se uma tarefa importante com esses componentes. Mesmo com uma simples chamada de rede, você precisa estar ciente de possíveis exceções, capturá-las e agir de acordo. Isso nos obriga a agrupar nossa resposta em uma classe personalizada contendo os dados, com possíveis informações de erro, e um sinalizador indica se a ação foi bem-sucedida ou não.
Isso é muito trabalho a fazer para cada chamada. Felizmente, agora existe uma solução muito melhor e mais simples disponível: RxJava.
Opção 3: RxJava
Você já deve ter ouvido falar do RxJava, a biblioteca desenvolvida pela Netflix. É quase mágica em Java.
O RxAndroid permite que você use o RxJava no Android e facilita muito lidar com tarefas assíncronas. Você pode aprender mais sobre RxJava no Android aqui.
O RxJava fornece dois componentes: Observer
e Subscriber
.
Um observador é um componente que contém alguma ação. Ele executa essa ação e retorna o resultado se for bem-sucedido ou um erro se falhar.
Um assinante , por outro lado, é um componente que pode receber o resultado (ou erro) de um observável, assinando-o.
Com o RxJava, você primeiro cria um observável:
Observable.create((ObservableOnSubscribe<Data>) e -> { Data data = mRestApi.getData(); e.onNext(data); })
Depois que o observável for criado, você poderá assiná-lo.
Com a biblioteca RxAndroid, você pode controlar o encadeamento no qual deseja executar a ação no observável e o encadeamento no qual deseja obter a resposta (ou seja, o resultado ou erro).
Você encadeia o observável com estas duas funções:
.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()
Schedulers são componentes que executam a ação em um determinado thread. AndroidSchedulers.mainThread()
é o agendador associado ao encadeamento principal.
Dado que nossa chamada de API é mRestApi.getData()
e ela retorna um objeto Data
, a chamada básica pode ser assim:
Observable.create((ObservableOnSubscribe<Data>) e -> { try { Data data = mRestApi.getData(); e.onNext(data); } catch (Exception ex) { e.onError(ex); } }) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(match -> Log.i(“rest api, "success"), throwable -> Log.e(“rest api, "error: %s" + throwable.getMessage()));
Sem sequer entrar em outros benefícios de usar o RxJava, você já pode ver como o RxJava nos permite escrever um código mais maduro abstraindo a complexidade do threading.
Caso de uso nº 3: encadeamento de chamadas de rede
Para chamadas de rede que precisam ser executadas em sequência (ou seja, onde cada operação depende da resposta/resultado da operação anterior), você precisa ter um cuidado especial ao gerar código espaguete.
Por exemplo, talvez seja necessário fazer uma chamada de API com um token que você precisa buscar primeiro por meio de outra chamada de API.
Opção 1: AsyncTask ou carregadores
Usar AsyncTask
ou carregadores quase definitivamente levará ao código de espaguete. A funcionalidade geral será difícil de acertar e exigirá uma quantidade enorme de código clichê redundante em todo o projeto.
Opção 2: RxJava usando flatMap
Em RxJava, o operador flatMap
pega um valor emitido do observável de origem e retorna outro observável. Você pode criar um observável e depois criar outro observável usando o valor emitido do primeiro, que basicamente os encadeará.
Etapa 1. Crie o observável que busca o token:
public Observable<String> getTokenObservable() { return Observable.create(subscriber -> { try { String token = mRestApi.getToken(); subscriber.onNext(token); } catch (IOException e) { subscriber.onError(e); } }); }
Etapa 2. Crie o observável que obtém os dados usando o token:
public Observable<String> getDataObservable(String token) { return Observable.create(subscriber -> { try { Data data = mRestApi.getData(token); subscriber.onNext(data); } catch (IOException e) { subscriber.onError(e); } }); }
Etapa 3. Encadeie os dois observáveis e assine:
getTokenObservable() .flatMap(new Function<String, Observable<Data>>() { @Override public Observable<Data> apply(String token) throws Exception { return getDataObservable(token); } }) .subscribe(data -> { doSomethingWithData(data) }, error -> handleError(e));
Observe que o uso dessa abordagem não se limita a chamadas de rede; ele pode funcionar com qualquer conjunto de ações que precisem ser executadas em uma sequência, mas em threads separados.
Todos os casos de uso acima são bastante simples. A alternância entre threads só acontecia depois que cada um terminava sua tarefa. Cenários mais avançados — por exemplo, onde dois ou mais threads precisam se comunicar ativamente entre si — também podem ser suportados por essa abordagem.
Caso de uso nº 4: comunicar-se com o encadeamento da interface do usuário de outro encadeamento
Considere um cenário em que você gostaria de fazer upload de um arquivo e atualizar a interface do usuário depois de concluído.
Como o upload de um arquivo pode levar muito tempo, não há necessidade de deixar o usuário esperando. Você pode usar um serviço, e provavelmente IntentService
, para implementar a funcionalidade aqui.
Nesse caso, no entanto, o maior desafio é poder invocar um método no thread da interface do usuário após a conclusão do upload do arquivo (que foi realizado em um thread separado).

Opção 1: RxJava dentro do serviço
RxJava, sozinho ou dentro de um IntentService
, pode não ser o ideal. Você precisará usar um mecanismo baseado em retorno de chamada ao assinar um Observable
, e IntentService
é criado para fazer chamadas síncronas simples, não retornos de chamada.
Por outro lado, com um Service
, você precisará interromper manualmente o serviço, o que exige mais trabalho.
Opção 2: BroadcastReceiver
O Android fornece esse componente, que pode ouvir eventos globais (por exemplo, eventos de bateria, eventos de rede etc.), bem como eventos personalizados. Você pode usar esse componente para criar um evento personalizado que é acionado quando o upload é concluído.
Para fazer isso, você precisa criar uma classe personalizada que estenda BroadcastReceiver
, registrá-la no manifesto e usar Intent
e IntentFilter
para criar o evento personalizado. Para acionar o evento, você precisará do método sendBroadcast
.
Manifesto:
<receiver android:name="UploadReceiver"> <intent-filter> <action android:name="com.example.upload"> </action> </intent-filter> </receiver>
Receptor:
public class UploadReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getBoolean(“success”, false) { Activity activity = (Activity)context; activity.updateUI(); } }
Remetente:
Intent intent = new Intent(); intent.setAction("com.example.upload"); sendBroadcast(intent);
Esta abordagem é uma opção viável. Mas, como você notou, isso envolve algum trabalho, e muitas transmissões podem atrasar as coisas.
Opção 3: usando o manipulador
Um Handler
é um componente que pode ser anexado a um encadeamento e, em seguida, feito para executar alguma ação nesse encadeamento por meio de mensagens simples ou tarefas Runnable
. Ele funciona em conjunto com outro componente, Looper
, que é responsável pelo processamento de mensagens em uma determinada thread.
Quando um Handler
é criado, ele pode obter um objeto Looper
no construtor, que indica a qual thread o handler está anexado. Se você deseja usar um manipulador anexado ao encadeamento principal, você precisa usar o looper associado ao encadeamento principal chamando Looper.getMainLooper()
.
Nesse caso, para atualizar a interface do usuário de um thread em segundo plano, você pode criar um manipulador anexado ao thread da interface do usuário e postar uma ação como um Runnable
:
Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // update the ui from here } });
Essa abordagem é muito melhor que a primeira, mas há uma maneira ainda mais simples de fazer isso…
Opção 3: usando EventBus
EventBus
, uma biblioteca popular da GreenRobot, permite que os componentes se comuniquem com segurança entre si. Como nosso caso de uso é aquele em que queremos apenas atualizar a interface do usuário, essa pode ser a escolha mais simples e segura.
Etapa 1. Crie uma classe de evento. por exemplo, UIEvent
.
Etapa 2. Inscreva-se no evento.
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {/* Do something */}; register and unregister eventbus : @Override public void onStart() { super.onStart(); EventBus.getDefault().register(this); } @Override public void onStop() { super.onStop(); EventBus.getDefault().unregister(this); }
Etapa 3. Poste o evento: EventBus.getDefault().post(new UIEvent());
Com o parâmetro ThreadMode
na anotação, você está especificando o encadeamento no qual gostaria de se inscrever para este evento. Em nosso exemplo aqui, estamos escolhendo o thread principal, pois queremos que o receptor do evento possa atualizar a interface do usuário.
Você pode estruturar sua classe UIEvent
para conter informações adicionais conforme necessário.
No serviço:
class UploadFileService extends IntentService { // … Boolean success = uploadFile(File file); EventBus.getDefault().post(new UIEvent(success)); // ... }
Na atividade/fragmento:
@Subscribe(threadMode = ThreadMode.MAIN) public void onUIEvent(UIEvent event) {//show message according to the action success};
Usando a EventBus library
, a comunicação entre threads se torna muito mais simples.
Caso de uso nº 5: comunicação bidirecional entre threads com base nas ações do usuário
Suponha que você esteja construindo um media player e queira que ele continue tocando música mesmo quando a tela do aplicativo estiver fechada. Neste cenário, você desejará que a interface do usuário seja capaz de se comunicar com o encadeamento de mídia (por exemplo, reproduzir, pausar e outras ações) e também desejará que o encadeamento de mídia atualize a interface do usuário com base em determinados eventos (por exemplo, erro, status do buffer , etc).
Um exemplo de player de mídia completo está além do escopo deste artigo. Você pode, no entanto, encontrar bons tutoriais aqui e aqui.
Opção 1: usando EventBus
Você pode usar EventBus
aqui. No entanto, geralmente não é seguro postar um evento do thread da interface do usuário e recebê-lo em um serviço. Isso ocorre porque você não tem como saber se o serviço está sendo executado quando você envia a mensagem.
Opção 2: usando o BoundService
Um BoundService
é um Service
vinculado a uma atividade/fragmento. Isso significa que a atividade/fragmento sempre sabe se o serviço está rodando ou não e, além disso, obtém acesso aos métodos públicos do serviço.
Para implementá-lo, você precisa criar um Binder
personalizado dentro do serviço e criar um método que retorne o serviço.
public class MediaService extends Service { private final IBinder mBinder = new MediaBinder(); public class MediaBinder extends Binder { MediaService getService() { // Return this instance of LocalService so clients can call public methods return MediaService.this; } } @Override public IBinder onBind(Intent intent) { return mBinder; } }
Para vincular a atividade ao serviço, você precisa implementar ServiceConnection
, que é a classe que monitora o status do serviço, e usar o método bindService
para fazer a vinculação:
// in the activity MediaService mService; // flag indicates the bound status boolean mBound; @Override protected void onStart() { super.onStart(); // Bind to LocalService Intent intent = new Intent(this, MediaService.class); bindService(intent, mConnection, Context.BIND_AUTO_CREATE); } private ServiceConnection mConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName className, IBinder service) { MediaBinder binder = (MediaBinder) service; mService = binder.getService(); mBound = true; } @Override public void onServiceDisconnected(ComponentName arg0) { mBound = false; } };
Você pode encontrar um exemplo de implementação completo aqui.
Para se comunicar com o serviço quando o usuário tocar no botão Reproduzir ou Pausar, você pode vincular ao serviço e chamar o método público relevante no serviço.
Quando há um evento de mídia e você deseja comunicá-lo de volta à atividade/fragmento, você pode usar uma das técnicas anteriores (por exemplo, BroadcastReceiver
, Handler
ou EventBus
).
Caso de uso nº 6: Executando ações em paralelo e obtendo resultados
Digamos que você esteja criando um aplicativo turístico e queira mostrar atrações em um mapa obtido de várias fontes (diferentes provedores de dados). Como nem todas as fontes podem ser confiáveis, você pode querer ignorar as que falharam e continuar a renderizar o mapa de qualquer maneira.
Para paralelizar o processo, cada chamada de API deve ocorrer em um thread diferente.
Opção 1: usando RxJava
No RxJava, você pode combinar vários observáveis em um usando os operadores merge()
ou concat()
. Você pode então se inscrever no observável “mesclado” e aguardar todos os resultados.
Essa abordagem, no entanto, não funcionará como esperado. Se uma chamada de API falhar, o observável mesclado relatará uma falha geral.
Opção 2: usando componentes Java nativos
O ExecutorService
em Java cria um número fixo (configurável) de threads e executa tarefas neles simultaneamente. O serviço retorna um objeto Future
que eventualmente retorna todos os resultados por meio do método invokeAll()
.
Cada tarefa que você envia para o ExecutorService
deve estar contida na interface Callable
, que é uma interface para criar uma tarefa que pode lançar uma exceção.
Depois de obter os resultados de invokeAll()
, você pode verificar todos os resultados e proceder de acordo.
Digamos, por exemplo, que você tenha três tipos de atração vindos de três endpoints diferentes e queira fazer três chamadas paralelas:
ExecutorService pool = Executors.newFixedThreadPool(3); List<Callable<Object>> tasks = new ArrayList<>(); tasks.add(new Callable<Object>() { @Override public Integer call() throws Exception { return mRest.getAttractionType1(); } }); // ... try { List<Future<Object>> results = pool.invokeAll(tasks); for (Future result : results) { try { Object response = result.get(); if (response instance of AttractionType1... {} if (response instance of AttractionType2... {} ... } catch (ExecutionException e) { e.printStackTrace(); } } } catch (InterruptedException e) { e.printStackTrace(); }
Dessa forma, você está executando todas as ações em paralelo. Você pode, portanto, verificar os erros em cada ação separadamente e ignorar as falhas individuais conforme apropriado.
Essa abordagem é mais fácil do que usar RxJava. É mais simples, mais curto e não falha em todas as ações por causa de uma exceção.
Caso de uso 7: consultando o banco de dados SQLite local
Ao lidar com um banco de dados SQLite local, é recomendável que o banco de dados seja usado a partir de um thread em segundo plano, pois chamadas de banco de dados (especialmente com bancos de dados grandes ou consultas complexas) podem consumir muito tempo, resultando no congelamento da interface do usuário.
Ao consultar dados SQLite, você obtém um objeto Cursor
que pode ser usado para buscar os dados reais.
Cursor cursor = getData(); String name = cursor.getString(<colum_number>);
Opção 1: usando RxJava
Você pode usar RxJava e obter os dados do banco de dados, assim como estamos obtendo dados do nosso back-end:
public Observable<Cursor> getLocalDataObservable() { return Observable.create(subscriber -> { Cursor cursor = mDbHandler.getData(); subscriber.onNext(cursor); }); }
Você pode usar o observável retornado por getLocalDataObservable()
da seguinte forma:
getLocalDataObservable() .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(cursor -> String name = cursor.getString(0), throwable -> Log.e(“db, "error: %s" + throwable.getMessage()));
Embora esta seja certamente uma boa abordagem, há uma que é ainda melhor, pois existe um componente que é construído apenas para esse cenário.
Opção 2: usando CursorLoader + ContentProvider
O Android fornece CursorLoader
, um componente nativo para carregar dados SQLite e gerenciar o thread correspondente. É um Loader
que retorna um Cursor
, que podemos usar para obter os dados chamando métodos simples como getString()
, getLong()
, etc.
public class SimpleCursorLoader extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> { public static final String TAG = SimpleCursorLoader.class.getSimpleName(); private static final int LOADER_ID = 0x01; private TextView textView; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.simple_cursor_loader); textView = (TextView) findViewById(R.id.text_view); getSupportLoaderManager().initLoader(LOADER_ID, null, this); } public Loader<Cursor> onCreateLoader(int i, Bundle bundle) { return new CursorLoader(this, Uri.parse("content://com.github.browep.cursorloader.data") , new String[]{"col1"}, null, null, null); } public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) { if (cursor != null && cursor.moveToFirst()) { String text = textView.getText().toString(); while (cursor.moveToNext()) { text += "<br />" + cursor.getString(1); cursor.moveToNext(); } textView.setText(Html.fromHtml(text) ); } } public void onLoaderReset(Loader<Cursor> cursorLoader) { } }
CursorLoader
funciona com o componente ContentProvider
. Este componente fornece uma infinidade de recursos de banco de dados em tempo real (por exemplo, notificações de alteração, gatilhos, etc.) que permitem aos desenvolvedores implementar uma melhor experiência do usuário com muito mais facilidade.
Não há solução Silver Bullet para threading no Android
O Android oferece muitas maneiras de lidar e gerenciar threads, mas nenhuma delas é uma bala de prata.
Escolher a abordagem de segmentação correta, dependendo do seu caso de uso, pode fazer toda a diferença para tornar a solução geral fácil de implementar e entender. Os componentes nativos se encaixam bem em alguns casos, mas não em todos. O mesmo se aplica a soluções sofisticadas de terceiros.
Espero que você ache este artigo útil ao trabalhar em seu próximo projeto Android. Compartilhe conosco sua experiência de encadeamento no Android ou em qualquer caso de uso em que as soluções acima funcionem bem - ou não funcionem - nos comentários abaixo.