Android Dev Summit, October 23-24: two days of technical content, directly from the Android team. Sign-up for livestream updates.

Visão geral dos processos e threads

Quando um componente de aplicativo é iniciado, e não há outro componente em execução, o sistema Android inicia um novo processo no Linux para o aplicativo com um único thread de execução. Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo e thread (chamado de thread “principal”). Se um componente de aplicativo for iniciado e já houver um processo para ele (se já existir outro componente), ele será iniciado dentro deste processo e usará o mesmo thread de execução. No entanto, é possível fazer com que vários componentes no aplicativo sejam executados em processos separados e criar threads adicionais para qualquer processo.

Este documento aborda como os processos e os threads funcionam em um aplicativo Android.

Processos

Por padrão, todos os componentes de um aplicativo são executados no mesmo processo, e não é possível alterar isso na maioria dos casos. No entanto, se você achar que precisa de controle sobre o processo relacionado a um determinado componente, será possível fazer isso no arquivo de manifesto.

A entrada do manifesto para cada tipo de elemento de componente — <activity>, <service>, <receiver> e <provider> — é compatível com um atributo android:process, que pode especificar um processo em que será necessário executar esse componente. É possível definir esse atributo para que cada componente seja executado no próprio processo ou para que alguns componentes compartilhem um processo, enquanto outros não. Também é possível definir android:process para que os componentes de aplicativos diferentes sejam executados no mesmo processo — desde que os aplicativos possam compartilhar o mesmo código de usuário do Linux e ser assinados com os mesmos certificados.

O elemento <application> também aceita um atributo android:process para definir um valor padrão que se aplique a todos os elementos.

O Android pode decidir desativar um processo em certo ponto, quando a memória estiver baixa e for necessária para outros processos que atendem o usuário de maneira mais imediata. Os componentes de aplicativo em execução no processo excluído serão consequentemente eliminados. Um processo será iniciado novamente para aqueles componentes quando houver trabalho para eles.

Ao decidir qual dos processos será eliminado, o sistema Android avalia a importância relativa dele para o usuário. Por exemplo, ele desativará mais rapidamente um processo que hospede atividades que não estejam mais visíveis na tela do que um que hospede atividades visíveis. A decisão é exterminar um processo ou não. Por isso, ela depende do estado dos componentes em execução neste processo.

Os detalhes do ciclo de vida do processo e da relação dele com os estados do aplicativo são discutidos em Ciclo de vida dos processos e do aplicativo.

Threads

Quando um aplicativo é executado, o sistema cria um thread de execução para ele, que é chamado de “principal”. Esse thread é muito importante porque ele é encarregado de despachar eventos para os widgets adequados da interface do usuário, incluindo eventos de desenho. Quase sempre, é também o thread em que o aplicativo interage com componentes do kit de ferramentas da IU do Android (componentes dos pacotes android.widget e android.view). Portanto, o thread principal também pode se chamar thread de IU. No entanto, em circunstâncias especiais, o thread principal de um aplicativo pode não ser o thread de IU. Para mais informações, consulte Anotações de thread.

O sistema não cria um thread separado para cada instância de um componente. Todos os componentes executados no mesmo processo são instanciados no thread de IU, e as chamadas do sistema para cada componente são despachadas a partir deste thread. Consequentemente, métodos que respondam aos callbacks do sistema (como onKeyDown() para informar ações de usuário ou um método callback do ciclo de vida) sempre serão executados no thread de IU do processo.

Por exemplo, quando o usuário toca em um botão na tela, o thread de IU do aplicativo despacha o evento de toque para o widget que, em troca, ativa o estado pressionado e publica uma solicitação de invalidação à fila do evento. O thread de IU retira a solicitação da fila e notifica o widget de que ele deve desenhar novamente por conta própria.

Quando o aplicativo realiza trabalho intenso em resposta à interação do usuário, esse modelo de um thread pode produzir desempenho inferior, a não ser que você implemente o aplicativo adequadamente. Especificamente, se tudo estiver acontecendo no thread de IU, realizar operações longas, como acesso à rede ou consultas a banco de dados, bloqueará toda a IU. Quando o thread é bloqueado, nenhum evento pode ser despachado, incluindo eventos de desenho. Da perspectiva do usuário, o aplicativo parece travar. Pior ainda, se o thread de IU for bloqueado por mais do que alguns segundos (cerca de 5 segundos atualmente), o usuário receberá a vergonhosa mensagem “aplicativo não respondendo” (ANR). O usuário talvez decida fechar o aplicativo e desinstalá-lo se estiver descontente.

Além disso, o kit de ferramentas de IU do Android não é seguro para threads. Portanto, você não pode gerenciar a IU de um thread de trabalho. Gerencie a interface do usuário no thread de IU. A partir disso, há duas regras simples para o modelo de thread único do Android:

  1. Não bloquear o encadeamento da IU
  2. Não acessar o kit de ferramentas de IU do Android fora do encadeamento da IU

Threads de trabalho

Por causa do modelo de thread único descrito acima, é essencial para a capacidade de resposta da IU do aplicativo que você não bloqueie o thread de IU. Caso você queira realizar operações que não sejam instantâneas, faça isso em threads separados (threads de “segundo plano” ou “de trabalho”).

No entanto, você não pode atualizar a interface do usuário de qualquer thread diferente do thread de IU ou o thread “principal”.

Para resolver esse problema, o Android oferece várias maneiras de acessar o thread de IU a partir de outros threads. Abaixo há uma lista dos métodos que podem ajudar você:

Kotlin

fun onClick(v: View) {
    Thread(Runnable {
        // a potentially time consuming task
        val bitmap = processBitMap("image.png")
        imageView.post {
            imageView.setImageBitmap(bitmap)
        }
    }).start()
}

Java

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            // a potentially time consuming task
            final Bitmap bitmap =
                    processBitMap("image.png");
            imageView.post(new Runnable() {
                public void run() {
                    imageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Essa implementação é segura para threads: a operação em segundo plano é concluída a partir de um thread separado, enquanto a ImageView será sempre gerenciada a partir do thread de IU.

No entanto, à medida que a complexidade da operação aumenta, pode se tornar muito difícil manter esse tipo de código. Para gerenciar interações mais complexas com um thread de trabalho, use um Handler nele para processar as mensagens entregues pelo thread de IU. Talvez a melhor solução seja estender a classe AsyncTask, que simplifica a execução das tarefas do thread de trabalho que precisam interagir com a IU.

Uso de AsyncTask

AsyncTask permite que você trabalhe de forma assíncrona na interface do usuário. Ela realiza operações de bloqueio em um thread de trabalho e, em seguida, publica os resultados no thread de IU, sem que você precise lidar com os threads e/ou os gerenciadores.

Para usá-la, você precisa atribuir a subclasse AsyncTask e implementar o método de callback doInBackground(), que executa em um conjunto de threads de segundo plano. Para atualizar a IU, você deve implementar onPostExecute(), que entrega o resultado de doInBackground() e é executado no thread de IU para que seja possível atualizar a IU de forma segura. Depois, é possível executar a tarefa chamando execute() no thread de IU.

Leia a referência AsyncTask para compreender melhor o uso desta classe.

Métodos seguros para threads

Em algumas situações, os métodos implementados podem ser chamados a partir de mais de um thread. Por isso, eles precisam ser programados para serem seguros para threads.

Isso se aplica especialmente para métodos que podem ser chamados remotamente, como métodos em um serviço vinculado. Quando uma chamada em um método implementado em um IBinder tem origem no mesmo processo de execução de IBinder, o método é executado no thread do autor da chamada. No entanto, quando a chamada tiver origem em outro processo, o método será executado em um thread escolhido a partir de uma série de threads que o sistema mantém no mesmo processo que IBinder (ele não será executado no thread de IU do processo). Por exemplo, enquanto o método onBind() de um serviço seria chamado a partir de um thread de IU do processo de um serviço, os métodos implementados no objeto que onBind() retorna (por exemplo, uma subclasse que implementa métodos de RPC) seriam chamados a partir dos threads no conjunto. Como um serviço pode ter mais de um cliente, mais de um thread no conjunto pode envolver o mesmo método IBinder ao mesmo tempo. Assim, os métodos IBinder precisam ser implementados para serem seguros para threads.

Da mesma forma, um provedor de conteúdo pode receber solicitações de dados originadas em outros processos. Apesar de as classes ContentResolver e ContentProvider ocultarem os detalhes de como a comunicação entre processos é gerenciada, os métodos ContentProvider que respondem a essas solicitações (query(), insert(), delete(), update() e getType()) serão chamados de um conjunto de threads no processo do provedor de conteúdo, não no thread de IU para o processo. Como esses métodos podem ser chamados a partir de qualquer quantidade de threads ao mesmo tempo, eles também precisarão ser implementados para serem seguros para threads.

Comunicação entre processos

O Android oferece um mecanismo para comunicação entre processos (IPC) usando chamadas de procedimento remoto (RPCs), em que um método é chamado por uma atividade ou outro componente de aplicativo, mas é executado remotamente (em outro processo), com qualquer resultado retornado de volta ao autor da chamada. Isso acarreta a decomposição de uma chamada de método e de seus dados para um nível em que o sistema operacional possa entender, transmitindo-a do processo e do espaço de endereço locais ao processo e ao espaço de endereço remotos e, em seguida, remontando e restabelecendo a chamada lá. Os valores de retorno são transmitidos na direção oposta. O Android fornece todo o código para realizar essas transações de IPC para que você possa se concentrar em definir e implementar a interface de programação de RPC.

Para realizar o IPC, o aplicativo precisa usar bindService() para ser vinculado a um serviço. Para saber mais, consulte o guia do desenvolvedor Serviços.