Caso prefira, você encontrará todo esse material, em inglês, no site do Developer Android. A tradução e comentários dos materiais eu faço livremente para ajudar a comunidade que fala português.

domingo, 17 de abril de 2011

Processes e Threads

Quando um componente de aplicação inicia e a aplicação não tem outros componentes rodando, o sistema Android inicia outro processo Linux para a aplicação com uma single thread de execução. Por padrão, todos os componentes da mesma aplicação rodam em um mesmo processo e thread (chamada thread principal). Se um componente de aplicação inicia e existe um processo para aquela aplicação (pois outro componente da aplicação já existe), então o componente é iniciado dentro do processo e usa a mesma thread de execução. Contudo, você pode fazer com que diferentes componentes em sua aplicação rodem em processos separados e poderá também criar threads adicionais para quaisquer processos.

Processos

Por padrão, todos os componentes da mesma aplicação rodam em um mesmo processo e a maioria das aplicação não devem mudar isso. Mas, se você precisa de controle sobre quais processos um certo componente pertence, então você deve fazê-lo dentro do arquivo de manifesto da aplicação.

A entrada no manifesto para cada tipo de elemento componente da aplicação - <activity>, <service>, <receiver> e <provider> - suportam um atributo android:process que pode especificar o processo no qual o componente deverá rodar. Você pode configurar esse atributo para que cada componente rode em seu próprio processo e para que alguns componentes compartilhem processos enquanto outros não. Você pode também configurar android:process para que componentes de diferentes aplicações rodem em um mesmo processos - desde que as aplicações comportilhem o mesmo ID de usuário Linux e sejam assinados com os mesmos certificados.

O elemento <application> também suporta o atributo android:process para que se possa configurar um valor padrão que se aplica a todos os componentes.

O Android deverá decidir a matar algum processo em algum ponto, como quando a memória está baixa ou é requerida por outro processo que seja mais importante naquele momento. Componentes de aplicação rodando em um processo que está sendo morto são, consequentemente, mortos também. Um processo é iniciado novamente para esses componentes quando eles forem novamente requisitados.

Quando o Android está decidindo sobre qual processo matar, ele pesa sua importância relativa ao usuário. Por exemplo, é mais fácil o Android matar processos que hospedam atividades que não estão mais visíveis ao usuário que outros que estão visíveis. A decisão sobre terminar um processo ou não, portanto, depende do estado do componente rodando naquele processos.

Ciclo de vida do processo

O sistema Android tenta manter um processo de aplicação pelo maior tempo possível carregado mas eventualmente ele necessita remover processos antigos para liberar memória para novos e mais importantes processos. Para determinar que processos manter e quais matar o sistema colocar a cada processo um status de importância dentro de uma hierarquia baseada nos componentes que estão sendo rodados no processo e o estado de seus componentes. Processos com importância menor são eliminados primeiro e então aqueles subsequentes até o mais importante.

Existem cinco níveis de importância na hierarquia. A lista seguinte mostra os diferentes tipos de processos em ordem de importância (sendo o primeiro o mais importante e que será morto por último dentro da hierarquia).

  • Foreground Process (processos que rodam em aplicações que estão na tela do usuário)
    Um processo que é requerido para o que o usuário está fazendo naquele momento. Um processo é considerado em modo foreground se alguma das condições abaixo forem verdadeiras:
    • O processo hospeda uma Activity na qual o usuário está interagindo (quando o método onResume() da atividade foi chamado);
    • O processo hospeda um Service que está ligado a atividade na qual o usuário está interagindo;
    • O processo hospeda um Service que está rodando em modo foreground - o serviço chamou startForeground();
    • O processo hospeda um Service que está executando alguns dos callbacks do ciclo de vida (onCreate(), onStart(), onDestroy());
    • O processo hospeda um BroadcastReceiver() que está executando o método onReceive().
    Geralmente apenas poucos processos em modo foreground existem em qualquer momento. Eles são mortos apenas como último recursos - no caso da memória estar tão baixa que não pode sequer continuar rodando.
  • Processo visível
    Um processo que não tem componentes em modo foreground mas que ainda assim afetam o que o usuário vê na tela. Um processo é considerado visível se uma das sequintes condições forem verdadeiras:
    • O processo hospeda uma Activity que não está em foreground mas continua visível para o usuário (seu método onPause() foi chamado). Isso pode ocorrer, por exemplo, se uma atividade em modo foreground chamou um diálogo que permite que a atividade anterior possa ser vista por ele (diálogo flutuante e transparente, por exemplo).
    • O processo hospeda um Service que está ligado a uma atividade visível em modo foreground.
    Um processo visível é considerado extremamente importante e não será morto a não ser que retirá-lo da memória seja parte do que a atividade que está rodando necessita (i.e. o processo foi morto de maneira declarada via código).
  • Processo de serviço
    Um processo que esteja rodando um serviço que foi inicializado com o método startService() e não está dentro das duas categorias anteriores. Apesar desses serviços não estarem diretamente ligados ao que o usuário está fazendo no momento, eles são geralmente relacionados com atividades que o usuário está desempenhando, como ouvir música ou fazendo o download de dados da rede. Então o sistema mantém esse processo ativo a não ser que necessite da memória usada por ele.
  • Serviços em Background
    Um processo que hospeda uma atividade que não está mais visível ao usuário (o método onStop() foi chamado). Esses processos não tem impacto direto na experiência do usuário e podem ser mortos pelo sistema para liberar memória.
  • Processos vazios
  • Um processo que não hospeda qualquer atividade que esteja ativa. A única razão de se manter esse processo ativo é por razões de caching, para que ele seja carregado mais rapidamente quando o usuário precisar dele.
O Android faz um ranking dos processos no nível mais alto que eles possam estar, baseado na importância dos componentes ativos no momento.

Em adição a isso, o ranking de um determinado processo será aumentado se outros processos dependem dele.

Threads

Quando uma aplicação é lançada o sistema cria uma thread de execução para aquela aplicação, chamada de thread principal. Essa thread é muito importante pois ela é encarregada de fazer o gerenciamento dos processos subsequentes. Ela também é chamada de UI Thread ou Thread de Interface.

O sistema NÃO cria uma thread separada para cada instância de um componente. Todos os componentes que rodam em um mesmo processo são instanciados na mesma UI Thread e as chamadas a cada componente que o sistema faz são despachadas a partir dessa thread. Consequentemente, métodos que respondem a chamadas callback do sistema (como onKeyDown()) sempre rodam na UI Thread do processo.

Por conta disso, quando um usuário toca um botão na tela, a UI Thread de sua aplicação envia um evento touch para o widget que então modifica seu estado para pressionado e posta uma requisição dizendo que é inválido. A UI Thread retira da fila a requisição e notifica o widget que ele tem de ser redesenhado (é quando vemos a imagem do botão como pressionado).

Quando sua aplicação realiza trabalho intensivo em resposta a uma interação do usuário, uma thread simples pode começar a ter uma resposta pobre a não ser que você faça implementações em sua aplicação. Especificamente, se tudo está rodando na UI Thread, realizar longas tarefas como um acesso a rede ou banco de dados por bloquear toda a interface da sua aplicação enquanto essa tarefa estiver rodando. Pior, a aplicação pode demorar tanto a responder que o sistema Android poderá entender que ela está travada e mostrar a mensagem de "application not responding". O usuário então terá de decidir se sair da aplicação ou se a desinstala.

Worker thread

Com o modelo de thread única mostrada acima, é vital que a responsividade de sua interface de aplicação não bloqueie a UI Thread. Se as operações que são realizadas não são instantâneas então você deve se certificar de criá-las em threads separadas (threads backgroud ou worker threads).

Por exemplo, abaixo está um código de um listener click que faz o download de uma imagem em uma thread separada e a mostra dentro de um 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();
}

Inicialmente, parece ser um código perfeito, já que ele cria uma nova thread e manuseia a operação de rede. No entanto, ele viola a segunda regra do modelo de thread única: não acessar a toolkit de interface do Android de fora da UI Thread. Esse exemplo modifica um ImageView a partir de um Worker Thread ao invés da UI Thread.

Para corrigir esse problema, o Android oferece diversas maneiras de acessar a UI Thread de outras threads. Aqui a lista de alguns métodos que podem ajudá-lo nessa tarefa:
  • Activity.runOnUiThread(Runnable)
  • View.post(Runnable)
  • View.postDelayed(Runnable, long)
Por exemplo, você poderá 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 sua implementação está em modo thread-safe: a operação de rede é realizada em uma thread separada enquanto a ImageView é manipulada pela UI Thread.

Só que com isso a complexidade de sua operação aumenta e esse código pode ficar complicado de manter. Para diminuir tal dificuldade em interações mais complexas com a worker thread, você deve considerar usar um Handler em seu worker thread para processar mensagens entregues à UI Thread. Talves a melhor solução, no entanto, seja extender a classe AsyncTask que simplifica a execução das tarefas worker thread que precisam interagir com a interface.

Usando AsyncTask

AsyncTask permite a você realizar trabalhos assincronos na sua interface de usuário. Ele realiza operações de bloqueio em worker threads e então publica seu resultado na UI Thread sem requerer que você manuseie threads ou handlers.

Para usá-lo, você deve criar uma subclass de AsyncTask e implementar o método doInBackground() que roda num pool de threads de background. Para atualizar sua interface, você deve implementar um onPostExecute() que entrega o resultado de doInBackground() e roda dentro da UI Thread para que você possa atualizar de maneira segura a interface. Você pode, então, roda essa tarefa chamando execute() direto da thread.
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);
    }
}

Veja como o código agora está mais seguro e mais fácil de manter, já que separa o trabalho.

Você deveria ler o material de AsyncTask para uma referência completa e para entender completamente o uso dessa classe, mas aqui vai um overview de como ela funciona:
  • Você pode especificar o tipo de parâmetro, os valores de progresso e o valor final da tarefa, usando generics.
  • O método doInBackground() é executado automaticamente na worker thread.
  • onPreExecute(), onPostExecute() e onProgressUpdate() são chamados na UI Thread.
  • O valor retornado pelo doInBackground() é enviado para onPostExecute()
  • Você pode chamar publishProgress a qualquer momento em doInBackground() para executar onProgressUpdate() na UI Thread.
  • Você pode cancelar a tarefa a qualquer momento a partir de qualquer thread.
Métodos Thread-Safe

Em algumas situações, os métodos que você implementa devem ser chamados de mais de uma thread e, portanto, devem ser escritas como thread-safe.

Isso é primariamente verdadeiro para métodos que podem ser chamados remotamente - como métodos em um bound service. Quando uma chamada a um método implementado em um IBinder é originado em um mesmo processo no qual o IBinder está rodando, o método é executado na thread de quem o chama. No entanto, quando a chamada se origina em outro processo, o método é executado em uma thread escolhida de um pool de threads que o sistema mantém no mesmo processo do IBinder. Por exemplo, se um método onBind() fosse chamado a partir da UI Thread os métodos implementados em onBind() retornados seriam chamados em uma thread escolhida a partir do pool de threads. Como o serviço pode ter mais de um cliente, mais de um thread da pool pode tentar executar o mesmo método IBinder. Por essa razão, métodos IBinder devem ser implementados como sendo thread-safe.

Similarmente, um content provider pode receber dados requisitados que originam-se em outros prcessos. Apesar das classes ContentResolver e ContentProvider esconder detalhes de como os processos de comunicação interprocessos são gerenciados, os métodos que respondem a essas requisições - query(), insert(), delete(), update() e getType() - são chamados de um pool de threads dentro dos processos do content provider e não dos processos da UI Thread. Como esses métodos podem ser chamados de diversas threads a cada vez, elas devem ser implementadas como sendo thread-safe.

0 comentários:

Portions of this page are modifications based on work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.
Related Posts Plugin for WordPress, Blogger...