Processos e Threads

Quickview

  • Tudo o aplicativo roda em seu próprio processo e todos os componentes do aplicativo rodam nesse processo, por padrão
  • Um processo lento, operações de bloqueio em uma atividade devem ser feitas em um novo thread, para evitar abrandar a interface do utilizador

Neste documento

  1. Processos
    1. Ciclo de vida do processo
  2. Fios
    1. Fios do trabalhador
    2. Linhas de roscamétodos seguros
  3. Comunicação inter-processo

Quando um componente da aplicação é iniciado e a aplicação não tem nenhum outro componente em execução, o sistema Android inicia um novo processo Linux para a aplicação com um único tópico de execução. Por padrão, todos os componentes da mesma aplicação são executados no mesmo processo e thread(chamado thread “principal”). Se um componente da aplicação é iniciado e já existe um processo para essa aplicação (porque existe outro componente da aplicação), então o componente é iniciado dentro desse processo e usa o mesmo thread de execução. No entanto, você pode organizar para que componentes diferentes em seu aplicativo sejam executados em processos separados e você pode criar threads adicionais para qualquer processo.

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

Processos

Por padrão, todos os componentes do mesmo aplicativo são executados no mesmo processo e a maioria dos aplicativos não devem alterar isso. No entanto, se você achar que precisa controlar a que processo um determinado componente pertence, você pode fazê-lo no arquivo de manifesto.

A entrada de manifesto para cada tipo de componente element-<activity>, <service>, <receiver>, e <provider>-suporta um atributo android:process que pode especificar o processo em que esse componente deve ser executado. Você pode definir esse atributo para que cada componente seja executado em seu próprio processo ou para que alguns componentes compartilhem um processo enquanto outros não o façam. Você também pode definirandroid:process para que componentes de diferentes aplicações sejam executados no mesmo processo – desde que as aplicações compartilhem o mesmo ID de usuário Linux e sejam assinadas com os mesmos certificados.

O elemento <application> também suporta um atributo android:process, para definir o valor adefault que se aplica a todos os componentes.

Android pode decidir desligar um processo em algum momento, quando a memória está baixa e é requerida por outros processos que estão servindo mais imediatamente ao usuário. Os componentes da aplicação em execução no processo que está morto são consequentemente destruídos. Um processo é iniciado novamente para esses componentes quando há novamente trabalho a ser feito.

Ao decidir que processos matar, o sistema Android pesa sua importância relativa para o usuário. Por exemplo, ele desliga mais prontamente um processo de hospedagem de atividades que não são mais visíveis na tela, em comparação com um processo de hospedagem de atividades visíveis. A decisão de toterminar um processo, portanto, depende do estado dos componentes em execução nesse processo. As regras usadas para decidir que processos devem ser encerrados são discutidas abaixo.

Ciclo de vida do processo

O sistema Android tenta manter um processo de aplicação durante o maior tempo possível, buteventualmente precisa remover processos antigos para recuperar a memória para processos novos ou mais importantes. Toddeterminar quais processos manter e quais matar, o sistema coloca cada processo em uma “hierarquia de importância” baseada nos componentes em execução no processo e o estado desses componentes. Processos com a menor importância são eliminados primeiro, depois aqueles com a menor importância seguinte, e assim por diante, conforme necessário para recuperar recursos do sistema.

Existem cinco níveis na hierarquia de importância. A lista a seguir apresenta os diferentes tipos de processos em ordem de importância (o primeiro processo é o mais importante e o último é o mais qualificado):

  1. Processo em primeiro plano

    Um processo que é necessário para o que o usuário está fazendo atualmente. Um processo é considerado como estando em primeiro plano se alguma das seguintes condições for verdadeira:

    • Aloja um Activity com o qual o usuário está interagindo (o método Activity‘s onResume() já foi chamado).
    • Aloja um Service que está ligado à atividade com a qual o usuário está interagindo.
    • Aloja um Service que está rodando “em primeiro plano”-o serviço chamou startForeground().
    • Aloja um Service que está executando uma de suas coletas de vida (onCreate(), onStart(), ou onDestroy()).
    • Aloja um BroadcastReceiver que está executando seu onReceive() método.

    Geralmente, apenas alguns processos em primeiro plano existem em um dado momento. Eles são mortos apenas no último resort-se a memória é tão baixa que não podem continuar todos a correr. Geralmente, nesse ponto, o dispositivo atingiu um estado de paginação de memória, então matar alguns processos em primeiro plano é necessário para manter a interface do usuário responsiva.

  2. Processo visível

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

    • Aloja um Activity que não está em primeiro plano, mas que ainda évisível para o usuário (seu método onPause() foi chamado). Isto pode ocorrer, por exemplo, se a atividade em primeiro plano iniciou um diálogo, que permite que a atividade anterior seja vista atrás dela.
    • Aloja um Service que está ligado a uma atividade visível (ou em primeiro plano).

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

  3. Processo de serviço

    Um processo que está executando um serviço que foi iniciado com o método startService() e não se enquadra em nenhuma das duas categorias mais elevadas. Embora os processos de serviço não estejam diretamente ligados a nada que o usuário vê, eles geralmente estão fazendo coisas que o usuário se importa (como tocar música em segundo plano ou baixar dados na rede), então o sistema os mantém em execução, a menos que não haja memória suficiente para mantê-los junto com todos os processos visíveis e em primeiro plano.

  4. Processo em segundo plano

    Um processo que mantém uma atividade que não é visível para o usuário no momento (o método da atividade onStop() foi chamado). Estes processos não têm direcionamento sobre a experiência do usuário, e o sistema pode matá-los a qualquer momento para recuperar a memória para o primeiro plano, visível, ou processo de serviço. Normalmente há muitos processos em segundo plano em execução, então eles são keptin uma lista de LRU (menos usada recentemente) para garantir que o processo com a atividade que foi mais recentemente vista pelo usuário é o último a ser morto. Se uma atividade implementa seu método de ciclo de vida corretamente, e salva seu estado atual, matar seu processo não terá um efeito visível na experiência do usuário, porque quando o usuário navega de volta para a atividade, a atividade restaura todo o seu estado visível. Veja o documento Activitiesdocument para informações sobre como salvar e restaurar o estado.

  5. Esvaziar processo

    Um processo que não contenha nenhum componente ativo da aplicação. A única razão para manter este tipo de processo vivo é para fins de cache, para melhorar o tempo de inicialização na próxima vez que um componente precisar ser executado nele. O sistema frequentemente mata esses processos de forma a equilibrar os recursos gerais do sistema entre os caches de processo e os caches de kernel subjacentes.

Android classifica um processo no nível mais alto que pode, baseado na importância dos componentes atualmente ativos no processo. Por exemplo, se um processo hospeda um serviço e uma atividade visível, o processo é classificado como um processo visível, não um processo de serviço.

Além disso, a classificação de um processo pode ser aumentada porque outros processos são dependentes dele – um processo que está servindo a outro processo nunca pode ser classificado abaixo do processo que está servindo. Por exemplo, se um provedor de conteúdo no processo A está servindo um cliente no processo B, ou se como serviço no processo A está ligado a um componente no processo B, o processo A é sempre considerado pelo menos tão importante quanto o processo B.

Porque um processo que executa um serviço é classificado mais alto do que um processo com atividades em segundo plano, uma atividade que inicia uma operação de longo prazo pode fazer bem em iniciar um serviço para essa operação, ao invés de criar um thread de trabalhador – especialmente se a operação provavelmente durará mais do que a atividade.Por exemplo, uma atividade que está carregando uma foto para um site deve iniciar um serviço para realizar o upload, para que o upload possa continuar em segundo plano, mesmo que o usuário deixe a atividade. Esta é a mesma razão pela qual os receptores de broadcast devem empregar serviços em vez de simplesmente colocar operações demoradas em uma thread.

Threads

Quando uma aplicação é lançada, o sistema cria uma thread de execução para a aplicação,chamada “principal”. Esta thread é muito importante porque é responsável por despachar eventos para os widgets apropriados da interface do usuário, incluindo eventos de desenho. É também a thread em que sua aplicação interage com componentes do conjunto de ferramentas da interface de usuário do Android (componentes dos pacotes android.widget e android.view). Como tal, a thread principal é também um pouco mais escalonada a thread UI.

O sistema não cria uma thread separada para cada instância de um componente. Todos os componentes que rodam no mesmo processo são instanciados na thread UI, e as chamadas do sistema ao componente toeach são despachadas a partir dessa thread. Consequentemente, métodos que respondem aos retornos do sistema (como onKeyDown() para relatar ações do usuário ou um método de retorno do ciclo de vida) sempre são executados no thread de IU do processo.

Por exemplo, quando o usuário toca um botão na tela, o thread de IU do aplicativo envia o evento de toque para o widget, que por sua vez define seu estado pressionado e lança uma solicitação inválida para a fila de eventos. O thread de IU desmarca a solicitação e notifica o widget que ele deve se redesenhar.

Quando seu aplicativo executa um trabalho intensivo em resposta à interação do usuário, este modelo de thread único pode render um desempenho ruim, a menos que você implemente seu aplicativo corretamente. Especificamente, se tudo estiver acontecendo na thread da IU, executar operações longas como consultas à base de ordenação de acesso à rede irá bloquear a IU inteira. Quando a thread está bloqueada, nenhum evento pode ser despachado, incluindo eventos de desenho. Da perspectiva do usuário, a aplicação parece estar pendurada. Pior ainda, se a thread da IU for bloqueada por mais de alguns segundos (cerca de 5 segundos atualmente) o usuário é apresentado com o famoso diálogo “application notresponding” (ANR). O usuário pode então decidir sair de sua aplicação e desinstalar o itif que está infeliz.

Adicionalmente, o conjunto de ferramentas de IU Andoid não é seguro para threads. Portanto, você não deve manipular sua interface de usuário a partir de uma thread operária – você deve fazer toda a manipulação na sua interface de usuário a partir da thread UI. Assim, existem simplesmente duas regras para o modelo de thread única do Android:

  1. Não bloquear a thread de IU
  2. Não acessar o conjunto de ferramentas de IU do Android de fora da thread de IU

Tows de operador

Por causa do modelo de thread única descrito acima, é vital para a capacidade de resposta da IU da sua aplicação que você não bloqueie a thread de IU. Se você tem operações a executar que não são instantâneas, você deve certificar-se de fazê-las em threads separadas (threads “background” ou “worker”).

Por exemplo, abaixo está algum código para um ouvinte de cliques que baixa uma imagem de uma thread separada e a exibe 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();}

No início, isto parece funcionar bem, porque cria uma nova thread para lidar com a operação da rede. No entanto, ele viola a segunda regra do modelo de rosca única: não acesse o conjunto de ferramentas de interface de usuário Android de fora da rosca de interface – este exemplo modifica a ImageView da rosca operária em vez da rosca de interface de usuário. Isto pode resultar em um comportamento inundado e inesperado, que pode ser difícil e demorado de rastrear.

Para corrigir este problema, o Android oferece várias maneiras de acessar a thread de IU a partir de outras threads. Aqui está uma lista de métodos que podem ajudar:

  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable,long)

Por exemplo, você pode corrigir 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 esta implementação é thread-safe: a operação de rede é feita a partir de uma thread separada enquanto que o ImageView é manipulado a partir da thread UI.

No entanto, à medida que a complexidade da operação cresce, este tipo de código pode tornar-se complicado e difícil de manter. Para lidar com interações mais complexas com uma thread worker, você pode considerar o uso de um Handler na thread worker, para processar mensagens entregues a partir da UIthread. Talvez a melhor solução, no entanto, seja estender a classe AsyncTask, o que simplifica a execução de tarefas de worker thread que precisam interagir com a UI.

Using AsyncTask

AsyncTask permite que você execute trabalho assíncrono na sua interface de usuário. Ele executa as operações de bloqueio em uma thread de operário e depois publica os resultados na thread da IU, sem exigir que você mesmo manuseie threads e/ou manipuladores.

Para usá-lo, você deve subclassificar AsyncTask e implementar o método doInBackground() callback, que roda em uma pool de threads de fundo. Para actualizar a sua IU, deve implementar onPostExecute(), que fornece o resultado de doInBackground() e corre na linha da IU, para que possa actualizar a sua IU em segurança. Você pode então executar a tarefa chamando execute() da thread de IU.

Por exemplo, você pode implementar o exemplo anterior usando AsyncTask thisway:

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); } /** 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 é segura e o código é mais simples, porque separa o trabalho na parte que deve ser feita em uma thread de operário e a parte que deve ser feita na thread de IU.

Você deve ler a referência AsyncTask para uma compreensão completa sobre como usar esta classe, mas aqui está uma rápida visão geral de como ela funciona:

  • Você pode especificar o tipo dos parâmetros, os valores de progresso, e o valor final da tarefa, usando genéricos
  • O método doInBackground() executa automaticamente em um thread worker
  • onPreExecute(), onPostExecute(), e onProgressUpdate() são todos invocados no fio UI
  • O valor retornado por doInBackground() é enviado paraonPostExecute()
  • Você pode chamar publishProgress() a qualquer momento em doInBackground() para executar onProgressUpdate() no fio UI
  • Você pode cancelar a tarefa a qualquer momento, de qualquer fio

Cautela: Outro problema que você pode encontrar ao usar uma thread workerthread é o reinício inesperado de sua atividade devido a uma mudança na configuração do tempo de execução (como quando o usuário muda a orientação da tela), o que pode destruir a thread worker. Veja como você pode persistir na tarefa durante um desses reinícios e como cancelar corretamente a tarefa quando a atividade é destruída, veja o código fonte da aplicação de amostra Shelves.

Métodos thread-safe

Em algumas situações, os métodos que você implementa podem ser chamados a partir de mais de uma thread, e portanto devem ser escritos para serem thread-safe.

Isso é principalmente verdade para métodos que podem ser chamados remotamente – como os métodos em um serviço vinculado. Quando uma chamada sobre um método implementado em um IBinder se origina no mesmo processo em que o IBinder está sendo executado, o método é executado na thread do chamador. Entretanto, quando a chamada se origina em outro processo, o método é executado em uma thread escolhida de threads que o sistema mantém no mesmo processo que o IBinder (não é executado na thread UI do processo). Por exemplo, enquanto o métodoonBind() de um serviço seria chamado a partir da thread UI do processo do serviço, métodos implementados no objeto que onBind() retorna (por exemplo, uma subclasse que implementa métodos RPC) seriam chamados a partir de threads no pool. Como um serviço pode ter mais de um cliente, mais de uma thread do pool pode envolver o mesmo método IBinder ao mesmo tempo. IBinder métodos devem, portanto, ser implementados para serem thread-safe.

Da mesma forma, um provedor de conteúdo pode receber solicitações de dados que se originam em outros processos.Embora os métodos ContentResolver e ContentProviderclasses ocultem os detalhes de como a comunicação entre processos é gerenciada, ContentProvider métodos que respondem a essas solicitações – os métodos query(), insert(), delete(), update(), e getType()– são chamados a partir de um pool de threads no processo do provedor de conteúdo, não o UIthread para o processo. Como esses métodos podem ser chamados a partir de qualquer número de threads ao mesmo tempo, eles também devem ser implementados para serem seguros contra threads.

Comunicação inter-processo

Android oferece um mecanismo para comunicação inter-processo (IPC) usando chamadas de procedimento remoto(RPCs), no qual um método é chamado por uma atividade ou outro componente de aplicação, mas executado de forma remota (em outro processo), com qualquer resultado retornado para o chamador. Isso implica em decompor uma chamada de método e seus dados em um nível que o sistema operacional possa compreender, transmitindo-o do processo local e espaço de endereçamento para o processo remoto e espaço de endereçamento, em seguida, remontando e reencenando a chamada lá. Os valores de retorno são transmitidos na direção oposta. O Android fornece todo o código para realizar estas transações IPC, para que você possa se concentrar na definição e implementação da interface de programação RPC.

Para executar IPC, sua aplicação deve se ligar a um serviço, usando bindService(). Para mais informações, consulte o guia do desenvolvedor de Serviços.

Deixe um comentário