Processos e encadeamentos

Quando um componente de aplicativo inicia e o aplicativo não tem nenhum outro componente em execução, o sistema Android inicia um novo processo no Linux para o aplicativo com um único encadeamento de execução. Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo e encadeamento (chamado de encadeamento "principal"). Se um componente de aplicativo iniciar e já existir um processo para esse aplicativo (pois outro componente do aplicativo já existe), ele será iniciado dentro deste processo e usará o mesmo encadeamento de execução. No entanto, é possível organizar para que vários componentes no aplicativo sejam executados em processos separados e criar encadeamentos adicionais para qualquer processo.

Este documento discute como os processos e os encadeamentos funcionam em um aplicativo do Android.

Processos

Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo e a maioria dos aplicativos não deve alterar isso. No entanto, se achar que precisa de controle sobre o processo a que um certo componente pertence, é 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 em seu próprio processo ou para que alguns componentes compartilhem um processo, enquanto que outros não. Também é possível definir android:process para que os componentes de aplicativos diferentes sejam executados no mesmo processo — fornecido para que os aplicativos compartilhem o mesmo código de usuário do Linux e sejam 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 forma mais imediata. Os componentes de aplicativo em execução no processo que é eliminado são consequentemente eliminados. Um processo é iniciado novamente para aqueles componentes quando houver trabalho para eles.

Ao decidir que processo eliminar, o sistema Android avalia a importância relativa dele para o usuário. Por exemplo, ele fechará mais prontamente um processo que hospede atividades que não estejam mais visíveis na tela, comparado a um processo que hospede atividades visíveis. A decisão é exterminar processo ou não, portanto, depende do estado dos componentes em execução neste processo. As regras usadas para decidir quais processos serão exterminados são discutidas abaixo.

Ciclo de vida dos processos

O sistema Android tenta manter um processo do aplicativo pelo maior período possível, mas eventualmente precisa remover processos antigos para recuperar memória para processos novos e mais importantes. Para determinar quais processos manter e quais exterminar, o sistema posiciona cada um em uma "hierarquia de importância" com base nos componentes em execução e no estado deles. Processos com menos importância serão eliminados primeiro e, em seguida, aqueles com a menor importância depois deles também serão, consecutivamente, até que os recursos do sistema sejam recuperados.

Há cinco níveis na hierarquia de importância. A lista a seguir mostra os tipos de processo em ordem de importância (o primeiro processo é o mais importante e o eliminado por último):

  1. Processos em primeiro plano

    Um processo necessário para o que o usuário está fazendo. Considera-se um processo como em primeiro plano se qualquer uma das condições for verdadeira:

    Geralmente, somente poucos processos em primeiro plano existem em um determinado momento. Eles serão eliminados somente como último recurso — se a memória estiver baixa demais para suportar a execução de todos. Geralmente, neste ponto, o dispositivo atingiu o estado de paginação de memória. Portanto, eliminar alguns processos em primeiro plano é necessário para que a interface do usuário permaneça responsiva.

  2. Processos visíveis

    Um processo que não tenha nenhum componente em primeiro plano, mas que ainda possa afetar o que o usuário vê na tela. Um processo é considerado visível se uma das condições a seguir for verdadeira:

    • Se ele hospedar uma Activity que não está em primeiro plano, mas que ainda é visível para o usuário (seu método onPause() foi chamado). Isso pode ocorrer, por exemplo, se a atividade em primeiro plano iniciar uma caixa de diálogo, que permite que a atividade anterior seja vista por trás dela.
    • Se ele hospedar um Service que esteja vinculado a uma atividade visível (ou em primeiro plano).

    Um processo visível é considerado extremamente importante e não será eliminado a não ser que seja necessário para manter em execução os processos em primeiro plano.

  3. Processos de serviço

    Processos que executem um serviço que tenha sido iniciado com o método startService() e não esteja em uma das duas categorias mais altas. Apesar de processos de serviço não estarem diretamente ligados a tudo o que os usuários veem, eles estão geralmente fazendo coisas com que o usuário se importa (como reproduzindo música em segundo plano ou baixando dados na rede) e o sistema os mantém em execução a não ser que haja memória suficiente para retê-los juntamente com todos os processos visíveis e em primeiro plano.

  4. Processos em segundo plano

    Um processo que mantenha uma atividade que não esteja atualmente visível para o usuário (se o método onStop() da atividade tiver sido chamado). Esses processos não têm impacto direto na experiência do usuário e o sistema poderá eliminá-los a qualquer momento para recuperar memória para processos em primeiro plano, visíveis ou de serviço. Geralmente há vários processos em segundo plano em execução e eles são mantidos em uma lista LRU (least recently used - menos usados recentemente) para garantir que o processo com a atividade que foi mais exibição recentemente pelo usuário seja a última a ser eliminada. Se uma atividade implementar seu método de ciclo de vida corretamente e salvar o estado atual, eliminar seu processo não terá efeito visível na experiência do usuário, pois quando ele navegar de volta à atividade, ela restaurará todo o estado visível. Consulte o documento Atividades para obter informações sobre os estados de restauração e de gravação.

  5. Processo vazio

    Um processo que não tem nenhum componente de aplicativo ativo. O único motivo para manter esse tipo de processo ativo é para armazenamento em cache, para aprimorar o tempo de inicialização na próxima vez em que um componente precisar executá-lo. O sistema frequentemente elimina esses processos para equilibrar os recursos do sistema em geral entre os armazenamentos em cache do processo e os de núcleo subjacente.

O Android coloca o processo no maior nível possível, com base na importância dos componentes atualmente ativos no processo. Por exemplo, se um processo hospedar um serviço e uma atividade visível , ele terá a classificação de um processo visível, e não a de um processo de serviço.

Além disso, a classificação de um processo pode ser elevada porque outros processos dependem dele — um processo que está servindo a outro processo nunca pode ter classificação menor do que a do processo que está sendo servido. Por exemplo, se um provedor de conteúdo no processo A estiver servindo um cliente no processo B, ou se um serviço no processo A estiver vinculado a um componente no processo B, o processo A será sempre considerado menos importante do que o processo B.

Como um processo que executa um serviço tem classificação maior do que um processo com atividades em segundo plano, uma atividade que iniciar uma operação de longo prazo poderá também iniciar um serviço para essa operação, em vez de simplesmente criar um encadeamento de trabalho — especificamente se a operação exceder a atividade. Por exemplo, uma atividade que esteja fazendo upload de uma imagem para um site deverá iniciar um serviço para fazer o upload para que ele possa continuar em segundo plano mesmo se o usuário deixar a atividade. Usar um serviço garante que a operação tenha pelo menos a prioridade "processo de serviço", independente do que acontecer à atividade. Esse é o mesmo motivo pelo qual os receptores de transmissão devem empregar serviços em vez de simplesmente usar operações que consomem tempo em um encadeamento.

Encadeamentos

Quando um aplicativo é executado, o sistema cria um encadeamento de execução para ele, chamado de "principal". Esse encadeamento é muito importante, pois está encarregado de despachar eventos para os widgets adequados da interface do usuário, incluindo eventos de desenho. É também o encadeamento 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 encadeamento principal, às vezes, também chama o encadeamento da IU.

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

Por exemplo, quando o usuário toca em um botão na tela, o encadeamento da IU do aplicativo despacha o evento de toque para o widget que, em troca, ativa seu estado pressionado e publica uma solicitação de invalidação à fila do evento. O encadeamento da 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 encadeamento pode produzir desempenho inferior, a não ser que você implemente o aplicativo adequadamente. Especificamente, se tudo estiver acontecendo no encadeamento da IU, realizar operações longas, como acesso à rede ou consultas a banco de dados, bloqueará toda a IU. Quando o encadeamento é bloqueado, nenhum evento pode ser despachado, incluindo eventos de desenho. Da perspectiva do usuário, o aplicativo parece travar. Pior ainda, se o encadeamento da 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 poderá, então, decidir fechar o aplicativo e desinstalá-lo se estiver descontente.

Além disso, o kit de ferramentas de IU do Android não é seguro para encadeamentos. Portanto, você não deve processar a IU de um encadeamento de trabalho — deve-se realizar todo o tratamento da interface do usuário no encadeamento da IU. Com isso dito, há duas regras simples para o modelo de encadeamento ú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

Encadeamentos de trabalho

Por causa do modelo de encadeamento único descrito acima, é essencial para a capacidade de resposta da IU do aplicativo que você não bloqueie o encadeamento da IU. Caso tenha operações a realizar que não sejam instantâneas, você deverá se certificar de fazê-las em encadeamentos separados (encadeamentos de "segundo plano" ou "de trabalho").

Por exemplo, eis o código de uma escuta de clique que baixa uma imagem de um encadeamento separado e exibe-a em uma ImageView:

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            Bitmap b = loadImageFromNetwork("http://example.com/image.png");
            mImageView.setImageBitmap(b);
        }
    }).start();
}

À primeira vista, parece não apresentar problemas, pois ele cria um novo encadeamento para lidar com a operação de rede. No entanto, ele viola a segunda regra do modelo de encadeamento único: não acessar o kit de ferramentas de IU do Android de fora do encadeamento da IU — esse exemplo modifica o ImageView a partir do encadeamento de trabalho em vez de o encadeamento da IU. Isso pode resultar em comportamentos inesperados e indefinidos, que podem ser difíceis de rastrear e consumir bastante tempo.

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

Por exemplo, é possível resolver o código acima usando o método View.post(Runnable):

public void onClick(View v) {
    new Thread(new Runnable() {
        public void run() {
            final Bitmap bitmap =
                    loadImageFromNetwork("http://example.com/image.png");
            mImageView.post(new Runnable() {
                public void run() {
                    mImageView.setImageBitmap(bitmap);
                }
            });
        }
    }).start();
}

Agora essa implementação é segura para encadeamentos: a operação de rede é concluída a partir de um encadeamento separado enquanto que a ImageView é tratada no encadeamento da IU.

No entanto, à medida que a complexidade da operação aumenta, esse tipo de código pode se tornar complexo e difícil demais para ser mantido. Para lidar com interações mais complexas com um encadeamento de trabalho, considere usar um Handler nele para processar as mensagens entregues pelo encadeamento da IU. Apesar de que, talvez, a melhor solução seja estender a classe AsyncTask, que simplifica a execução das tarefas do encadeamento 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 encadeamento de trabalho e, em seguida, publica os resultados no encadeamento da IU, sem que você precise lidar com os encadeamentos e/ou os gerenciadores.

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

Por exemplo, é possível implementar o exemplo anterior usando AsyncTask da seguinte forma:

public void onClick(View v) {
    new DownloadImageTask().execute("http://example.com/image.png");
}

private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> {
    /** The system calls this to perform work in a worker thread and
      * delivers it the parameters given to AsyncTask.execute() */
    protected Bitmap doInBackground(String... urls) {
        return loadImageFromNetwork(urls[0]);
    }

    /** The system calls this to perform work in the UI thread and delivers
      * the result from doInBackground() */
    protected void onPostExecute(Bitmap result) {
        mImageView.setImageBitmap(result);
    }
}

Agora a IU está segura e o código está mais simples, pois ele separa o trabalho em uma parte que deve ser feita em um encadeamento de trabalho e outra parte que deve ser feita no encadeamento da IU.

Leia a referência AsyncTask para compreender melhor o uso desta classe, mas a seguir há uma visão geral rápida sobre como ela funciona:

Atenção: Outro problema que pode ocorrer ao usar um encadeamento de trabalho são reinicializações inesperadas da atividade devido a alterações na configuração em tempo de execução (como quando o usuário altera a orientação da tela), que podem eliminar o encadeamento de trabalho. Para ver como é possível manter uma tarefa durante uma dessas reinicializações e como cancelar adequadamente a tarefa quando a atividade for eliminada, consulte o código-fonte do aplicativo Shelves de exemplo.

Métodos seguros de encadeamento

Em algumas situações, os métodos implementados podem ser chamados a partir de mais de um encadeamento e, portanto, devem ser programados para serem seguros para encadeamento.

Isso é especialmente verdadeiro para métodos que podem ser cancelados remotamente — como métodos em um serviço vinculado. Quando uma chamada de um método implementado em um IBinder tiver origem no mesmo processo em que IBinder estiver em execução, o método será executado no encadeamento do autor da chamada. No entanto, quando a chamada tiver origem em outro processo, o método será executado em um encadeamento escolhido a partir de uma série de encadeamentos que o sistema mantém no mesmo processo que IBinder (ele não será executado no encadeamento da IU do processo). Por exemplo, enquanto o método onBind() de um serviço seria chamado a partir de um encadeamento da 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 encadeamentos no conjunto. Como um serviço pode ter mais de um cliente, mais de um encadeamento no conjunto pode envolver o mesmo método IBinder ao mesmo tempo. Os métodos IBinder devem, portanto, ser implementados para serem seguros para encadeamentos.

De forma semelhante, um provedor de conteúdo pode receber solicitações de dados que tenham origem 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 — os métodos query(), insert(), delete(), update() e getType() — serão chamados de um conjunto de encadeamentos no processo do provedor de conteúdo, não no encadeamento da IU para o processo. Como esses métodos podem ser chamados a partir de qualquer quantidade de encadeamentos ao mesmo tempo, eles também devem ser implementados para serem seguros para encadeamento.

Comunicação entre processos

O Android oferece um mecanismo para comunicação entre processos (IPC) usando chamadas de procedimento remoto (RPCs), onde 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 na 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 local e do espaço de endereço ao processo remoto e ao espaço de endereço 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 deve usar bindService() para vincular-se a um serviço. Para obter mais informações, consulte o guia do desenvolvedor Serviços.