Como processar ciclos de vida com componentes que os reconhecem Parte do Android Jetpack

Os componentes com reconhecimento de ciclo de vida executam ações em resposta a uma mudança no status do ciclo de vida de outro componente, como atividades e fragmentos. Esses componentes ajudam você a produzir códigos mais organizados e, com frequência, mais leves e mais fáceis de manter.

Um padrão comum é implementar as ações dos componentes dependentes nos métodos de ciclo de vida de atividades e fragmentos. No entanto, esse padrão leva a um código desorganizado e à proliferação de erros. Usando componentes com reconhecimento de ciclo de vida, é possível mover o código de componentes dependentes dos métodos de ciclo de vida para os próprios componentes.

O pacote androidx.lifecycle fornece classes e interfaces que permitem criar componentes com reconhecimento de ciclo de vida, que são componentes que podem ajustar automaticamente o próprio comportamento com base no estado atual do ciclo de vida de uma atividade ou um fragmento.

A maioria dos componentes de app definidos no Android Framework tem ciclos de vida anexados a eles. Os ciclos de vida são gerenciados pelo sistema operacional ou pelo código da estrutura em execução no processo. Eles são fundamentais para o modo como o Android funciona e seu aplicativo precisa respeitá-los. Não fazer isso pode provocar vazamentos de memória ou até mesmo falhas no aplicativo.

Imagine que temos uma atividade que mostra o local do dispositivo na tela. Uma implementação comum pode ser semelhante a esta:

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val callback: (Location) -> Unit
) {

    fun start() {
        // connect to system location service
    }

    fun stop() {
        // disconnect from system location service
    }
}

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        myLocationListener.start()
        // manage other components that need to respond
        // to the activity lifecycle
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Java

class MyLocationListener {
    public MyLocationListener(Context context, Callback callback) {
        // ...
    }

    void start() {
        // connect to system location service
    }

    void stop() {
        // disconnect from system location service
    }
}

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    @Override
    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, (location) -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        myLocationListener.start();
        // manage other components that need to respond
        // to the activity lifecycle
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
        // manage other components that need to respond
        // to the activity lifecycle
    }
}

Mesmo que esse exemplo pareça bom, em um app real você teria muitas chamadas que gerenciam a IU e outros componentes em resposta ao estado atual do ciclo de vida. O gerenciamento de vários componentes coloca uma quantidade considerável de código em métodos de ciclo de vida, como onStart() e onStop(), o que dificulta a manutenção.

Além disso, não há garantia de que o componente será iniciado antes da atividade ou de que o fragmento será interrompido. Isso é verdadeiro especialmente se for necessário executar uma operação de longa duração, como alguma verificação de configuração em onStart(). Isso pode causar uma disputa em que o método onStop() termina antes do onStart(), mantendo o componente ativo por mais tempo que o necessário.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this) { location ->
            // update UI
        }
    }

    public override fun onStart() {
        super.onStart()
        Util.checkUserStatus { result ->
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start()
            }
        }
    }

    public override fun onStop() {
        super.onStop()
        myLocationListener.stop()
    }

}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, location -> {
            // update UI
        });
    }

    @Override
    public void onStart() {
        super.onStart();
        Util.checkUserStatus(result -> {
            // what if this callback is invoked AFTER activity is stopped?
            if (result) {
                myLocationListener.start();
            }
        });
    }

    @Override
    public void onStop() {
        super.onStop();
        myLocationListener.stop();
    }
}

O pacote androidx.lifecycle fornece classes e interfaces que ajudam a lidar com esses problemas de forma resiliente e isolada.

Lifecycle

Lifecycle é uma classe que contém as informações sobre o estado do ciclo de vida de um componente (como uma atividade ou um fragmento) e permite que outros objetos observem esse estado.

O Lifecycle usa duas enumerações principais para rastrear o status do ciclo de vida do componente associado a ele:

Evento
Eventos de ciclo de vida que são despachados do framework e da classe Lifecycle. Esses eventos são mapeados para os eventos de callback em atividades e fragmentos.
Estado
O estado atual do componente rastreado pelo objeto Lifecycle.
Diagrama dos estados do ciclo de vida
Figura 1. Estados e eventos que compõem o ciclo de vida da atividade do Android.

Pense nos estados como nós de um gráfico e nos eventos como as arestas entre esses nós.

Uma classe pode monitorar o status do ciclo de vida do componente implementando a DefaultLifecycleObserver e substituindo métodos correspondentes, como onCreate, onStart etc. Depois, você pode adicionar um observador chamando o método addObserver() da classe Lifecycle e transmitindo uma instância do seu observador, conforme mostrado no exemplo a seguir:

Kotlin

class MyObserver : DefaultLifecycleObserver {
    override fun onResume(owner: LifecycleOwner) {
        connect()
    }

    override fun onPause(owner: LifecycleOwner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(MyObserver())

Java

public class MyObserver implements DefaultLifecycleObserver {
    @Override
    public void onResume(LifecycleOwner owner) {
        connect()
    }

    @Override
    public void onPause(LifecycleOwner owner) {
        disconnect()
    }
}

myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

No exemplo acima, o objeto myLifecycleOwner implementa a interface LifecycleOwner, que é explicada na seção a seguir.

LifecycleOwner

LifecycleOwner é uma única interface de método que indica que a classe tem um Lifecycle. Ela tem um único método, getLifecycle(), que precisa ser implementado pela classe. Caso esteja tentando gerenciar o ciclo de vida de todo um processo de aplicativo, consulte ProcessLifecycleOwner.

Essa interface abstrai a propriedade de um Lifecycle de classes individuais, como Fragment e AppCompatActivity, e permite gravar componentes que funcionem com elas. Qualquer classe de aplicativo personalizada pode implementar a interface LifecycleOwner.

Os componentes que implementam DefaultLifecycleObserver funcionam perfeitamente com aqueles que implementam LifecycleOwner, porque um proprietário pode fornecer um ciclo de vida, que um observador pode se registrar para monitorar.

Para o exemplo de rastreamento de local, é possível fazer a classe MyLocationListener implementar DefaultLifecycleObserver e, em seguida, inicializá-la com o Lifecycle da atividade no método onCreate(). Isso permite que a classe MyLocationListener seja autossuficiente, o que significa que a lógica para reagir a mudanças no status do ciclo de vida é declarada em MyLocationListener, em vez da atividade. Armazenar os componentes individuais na sua própria lógica torna a lógica de atividades e fragmentos mais fácil de gerenciar.

Kotlin

class MyActivity : AppCompatActivity() {
    private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) {
        myLocationListener = MyLocationListener(this, lifecycle) { location ->
            // update UI
        }
        Util.checkUserStatus { result ->
            if (result) {
                myLocationListener.enable()
            }
        }
    }
}

Java

class MyActivity extends AppCompatActivity {
    private MyLocationListener myLocationListener;

    public void onCreate(...) {
        myLocationListener = new MyLocationListener(this, getLifecycle(), location -> {
            // update UI
        });
        Util.checkUserStatus(result -> {
            if (result) {
                myLocationListener.enable();
            }
        });
  }
}

Um caso de uso comum é evitar invocar certos callbacks se o Lifecycle não estiver em bom estado no momento. Por exemplo, se o callback executar uma transação de fragmento depois que o estado da atividade for salvo, ele causará uma falha, então nunca é recomendável invocar esse callback.

Para facilitar esse caso de uso, a classe Lifecycle permite que outros objetos consultem o estado atual.

Kotlin

internal class MyLocationListener(
        private val context: Context,
        private val lifecycle: Lifecycle,
        private val callback: (Location) -> Unit
): DefaultLifecycleObserver {

    private var enabled = false

    override fun onStart(owner: LifecycleOwner) {
        if (enabled) {
            // connect
        }
    }

    fun enable() {
        enabled = true
        if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
            // connect if not connected
        }
    }

    override fun onStop(owner: LifecycleOwner) {
        // disconnect if connected
    }
}

Java

class MyLocationListener implements DefaultLifecycleObserver {
    private boolean enabled = false;
    public MyLocationListener(Context context, Lifecycle lifecycle, Callback callback) {
       ...
    }

    @Override
    public void onStart(LifecycleOwner owner) {
        if (enabled) {
           // connect
        }
    }

    public void enable() {
        enabled = true;
        if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
            // connect if not connected
        }
    }

    @Override
    public void onStop(LifecycleOwner owner) {
        // disconnect if connected
    }
}

Com essa implementação, a classe LocationListener tem reconhecimento total do ciclo de vida. Se for necessário usar o LocationListener de outra atividade ou fragmento, basta inicializá-lo. Todas as operações de configuração e desmontagem são gerenciadas pela própria classe.

Se uma biblioteca fornece classes que precisam trabalhar com o ciclo de vida do Android, recomendamos que você use componentes com reconhecimento de ciclo de vida. Seus clientes de biblioteca podem integrar facilmente esses componentes sem o gerenciamento manual do ciclo de vida no lado do cliente.

Implementar um LifecycleOwner personalizado

Fragmentos e atividades na Biblioteca de Suporte 26.1.0 e versões mais recentes já implementam a interface LifecycleOwner.

Caso você tenha uma classe personalizada que queira transformar em um LifecycleOwner é possível utilizar a classe LifecycleRegistry. Contudo, será necessário encaminhar eventos para essa classe, conforme mostrado no exemplo de código a seguir:

Kotlin

class MyActivity : Activity(), LifecycleOwner {

    private lateinit var lifecycleRegistry: LifecycleRegistry

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        lifecycleRegistry = LifecycleRegistry(this)
        lifecycleRegistry.markState(Lifecycle.State.CREATED)
    }

    public override fun onStart() {
        super.onStart()
        lifecycleRegistry.markState(Lifecycle.State.STARTED)
    }

    override fun getLifecycle(): Lifecycle {
        return lifecycleRegistry
    }
}

Java

public class MyActivity extends Activity implements LifecycleOwner {
    private LifecycleRegistry lifecycleRegistry;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        lifecycleRegistry = new LifecycleRegistry(this);
        lifecycleRegistry.markState(Lifecycle.State.CREATED);
    }

    @Override
    public void onStart() {
        super.onStart();
        lifecycleRegistry.markState(Lifecycle.State.STARTED);
    }

    @NonNull
    @Override
    public Lifecycle getLifecycle() {
        return lifecycleRegistry;
    }
}

Práticas recomendadas para componentes com reconhecimento de ciclo de vida

  • Mantenha seus controladores de IU (atividades e fragmentos) o mais simples possível. Eles não podem tentar adquirir os próprios dados. Em vez disso, use um ViewModel para fazer isso e observe um objeto LiveData para refletir as mudanças nas visualizações.
  • Tente escrever IUs baseadas em dados em que a responsabilidade do controlador é atualizar as visualizações à medida que os dados são modificados ou notificar as ações do usuário de volta para o ViewModel.
  • Inclua sua lógica de dados na classe ViewModel. ViewModel servirá como o conector entre o controlador de IU e o restante do app. Entretanto, tenha cuidado, porque não é responsabilidade do ViewModel buscar dados (por exemplo, de uma rede). Em vez disso, ViewModel precisa chamar o componente adequado para buscar os dados e, em seguida, fornecer o resultado para o controlador de IU.
  • Use Data Binding para manter uma interface simples entre suas visualizações e o controlador de IU. Isso permite tornar suas exibições mais declarativas e minimizar o código de atualização que você precisa escrever nas suas atividades e fragmentos. Caso você prefira fazer isso na linguagem de programação Java, use uma biblioteca como a Butter Knife para evitar códigos boilerplate e conseguir uma melhor abstração.
  • Se sua IU for complexa, considere criar uma classe presenter para lidar com as modificações de IU. Essa pode ser uma tarefa trabalhosa, mas que pode tornar seus componentes da IU mais fáceis de serem testados.
  • Evite referenciar um contexto View ou Activity no ViewModel. Se o ViewModel durar mais que a atividade (no caso de mudanças de configuração), sua atividade vazará e não será devidamente descartada pelo coletor de lixo.
  • Use corrotinas de Kotlin para gerenciar tarefas de longa duração e outras operações que podem ser executadas de forma assíncrona.

Casos de uso para componentes com reconhecimento de ciclo de vida

Componentes com reconhecimento de ciclo de vida podem facilitar muito o gerenciamento de ciclos de vida em diversos casos. Vejas alguns exemplos:

  • Alternar entre atualizações de localização aproximada ou detalhada. Use componentes com reconhecimento de ciclo de vida para ativar atualizações de localização detalhadas enquanto seu app de local estiver visível e mude para atualizações aproximadas quando o app estiver em segundo plano. O LiveData, um componente com reconhecimento de ciclo de vida, permite que o app atualize a IU automaticamente quando o usuário muda de local.
  • Parar e iniciar o armazenamento de vídeos em buffer. Use componentes com reconhecimento de ciclo de vida para iniciar o armazenamento de vídeos em buffer o mais rápido possível, mas adiar a reprodução até que o app seja totalmente iniciado. Você também pode usar componentes com reconhecimento de ciclo de vida para encerrar o armazenamento em buffer quando o app for destruído.
  • Iniciar e interromper a conectividade de rede. Use componentes com reconhecimento de ciclo de vida para ativar a atualização em tempo real (streaming) de dados de rede enquanto um app estiver em primeiro plano e também para pausá-la automaticamente quando ele estiver em segundo plano.
  • Pausar e retomar drawables animados. Use componentes com reconhecimento de ciclo de vida para pausar drawables animados quando o app estiver em segundo plano e retomar os drawables depois que ele voltar para o primeiro plano.

Gerenciar eventos "on stop"

Quando um Lifecycle pertence a um AppCompatActivity ou Fragment, o estado do Lifecycle muda para CREATED e o evento ON_STOP é enviado quando o AppCompatActivity ou o onSaveInstanceState() da Fragment é chamado.

Quando o estado de um Fragment ou uma AppCompatActivity é salvo pelo onSaveInstanceState(), a IU é considerada imutável até que ON_START seja chamado. Tentar modificar a IU depois que o estado foi salvo provavelmente causará inconsistências no estado de navegação do aplicativo. É por esse motivo que o FragmentManager gerará uma exceção se o app executar uma FragmentTransaction depois que o estado tiver sido salvo. Consulte commit() para mais detalhes.

LiveData impede que esse caso extremo ocorra, evitando chamar o observador se o Lifecycle associado ao observador não estiver pelo menos STARTED. Internamente, ele chama isAtLeast() antes de decidir invocar o observador.

Infelizmente, o método onStop() da AppCompatActivity é chamado depois de onSaveInstanceState(). Isso gera uma lacuna em que as mudanças de estado da IU não são permitidas, mas o Lifecycle ainda não foi passado para o estado CREATED.

Para evitar esse problema, a classe do Lifecycle na versão beta2 e anteriores marca o estado como CREATED sem despachar o evento para que qualquer código que verificar o estado atual receba o valor real, mesmo que o evento não seja despachado até onStop() ser chamado pelo sistema.

Essa solução tem dois grandes problemas:

  • Na API de nível 23 e anteriores, o sistema Android salva o estado de uma atividade, mesmo que ele esteja parcialmente coberto por outra atividade. Em outras palavras, o sistema Android chama onSaveInstanceState(), mas não necessariamente onStop(). Isso cria um intervalo potencialmente longo em que o observador ainda pensa que o ciclo de vida está ativo, mesmo que o estado da IU não possa ser modificado.
  • Qualquer classe que queira expor um comportamento semelhante ao da classe LiveData precisa implementar a solução alternativa fornecida pela versão beta 2 e anteriores do Lifecycle.

Outros recursos

Para saber mais sobre como lidar com ciclos de vida com componentes que os reconhecem, consulte os seguintes recursos.

Exemplos

Codelabs

Blogs