Programar em Android não é uma tarefa impossível, bastando ter conhecimentos em Java, pois a Google fornece uma API de acesso bem completa e documentada. Nesses dispositivos as tarefas podem ser executadas simultaneamente de duas formas: pelo “Paralelismo” em si e pelo PseudoParalelismo. O Paralelismo depende da quantidade de núcleos que o processador tem, ou seja, se ele tem dois núcleos, então apenas duas tarefas podem ser executadas simultaneamente. Agora, se ele tem apenas um núcleo, então ele executa apenas uma tarefa de cada vez, mas isso é inviável, pois não seria possível ouvir música, abrir o navegador e escrever um texto ao mesmo tempo.

Para esse problema ser solucionado nasceu o PseudoParalelismo, que nada mais é que um sistema interno que permite executar as tarefas um pouco por vez em um determinado tempo, ou seja, o escalonador de processos coloca a tarefa em execução por um tempo determinado e, acabando esse tempo, ocorre o “time-slice”, que tira o processo que está em execução e o coloca no final da fila dos processos que estão prontos para executar. Esse processo pode ir também para o estado bloqueado, significando que aguarda algum evento externo. Esse time-slice acontece em um tempo totalmente imperceptível, o que dá a sensação de paralelismo, mesmo quando existe apenas um único núcleo de processamento.

Dentro da plataforma Android pode-se executar tarefas em paralelo e para isso são utilizadas as Threads, que permitem a criação de linhas de execução a serem processadas em “paralelo". Isso é algo extremamente aconselhável que se faça no Android, já que esse acontece dentro da Activity principal.

O uso dessa prática é bem simples, mas para atualizar a interface gráfica com as informações processadas existe um trabalho um pouco maior, como a utilização de Handlers. A classe AsyncTask foi criada para facilitar esse tipo de processamento em background e atualizar os dados na Interface.

AsyncTask

A AsyncTask encapsula todo esse processo de criação de Threads e Handler. Para utilizá-la é bem simples, já que uma classe deve ser criada e estendida à classe AsyncTask. E por ser genérica, ela é definida da seguinte forma:

AsyncTask<Parâmetros,Progresso,Resultado>
  • Os parâmetros são o tipo de dados de qualquer valor que serão passados para os métodos da classe;
  • Progresso é o tipo de dado (integer) que mostrará o progresso na tela, como a porcentagem de download;
  • O Resultado é o retorno da Thread de processamento para a Thread principal que será trabalhado para ser exibido na interface do usuário.

Existem quatro métodos que podem ser sobrescritos na AsyncTask:

  • O método onPreExecute é executado sempre antes da Thread ser iniciada, e é onde são colocadas as mensagens a serem exibidas na tela para o usuário. Este não é obrigatório e executa na mesma Thread que a interface gráfica;
  • O método obrigatório doInBackground é o responsável por todo o processamento pesado, pois ele é executado em uma Thread separada. Enquanto é processada a informação, a tela do usuário não ficará travada. Seu retorno é passado por parâmetro para o método onPostExecute;
  • O método onPostExecute é o que recebe o retorno do doInBackground e é chamado utilizando um Handler. Sua execução se dá na mesma Thread que a interface gráfica;
  • O método onProgressUpdate é o responsável por receber as informações para mostrar a porcentagem do download na tela para o usuário. Também é executado na mesma Thread que a interface gráfica.

A Listagem 1 mostra a estrutura de uma classe que estende de AsyncTask.

public class SuaClasse extends AsyncTask<String,Integer,Integer>{
  @Override
  protected void onPreExecute(){
    //Codigo
  }
  @Override
  protected Integer doInBackground(String... params) {
    //Codigo
  }
  @Override
  protected void onPostExecute(Integer numero){
    //Codigo
  }
  protected void onProgressUpdate(Integer… params){
    //Codigo
  }

}
Listagem 4. Exemplo de como a classe vai ficar

Agora será mostrado um exemplo prático onde a aplicação irá receber uma URL contendo apenas uma imagem que será baixada e mostrada na tela do usuário. Todo processamento será feito em uma Thread separada.

O layout terá três elementos: um EditText que receberá a URL, um Button para acionar a ação de baixar a imagem e um ImageView responsável por mostrar o Bitmap na tela. A Listagem 2 mostra esse código.

<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent"
    android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
 
    <EditText
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/editText"
        android:layout_alignParentTop="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
 
    <Button
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Baixar"
        android:id="@+id/button"
        android:layout_below="@+id/editText"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
 
    <ImageView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:id="@+id/imageView"
        android:layout_below="@+id/button"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentBottom="true"
        android:layout_alignParentRight="true"
        android:layout_alignParentEnd="true" />
</RelativeLayout>
Listagem 2. Código XML do layout

A Figura 1 mostra como esse layout deve parecer.

Layout
Figura 1. Layout

O arquivo de manifesto deve conter as informações a seguir, mostradas na Listagem 3. A permissão de acessar a internet deve ser dada, pois a imagem a ser baixada será tirada de lá.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
  package="br.com.home.asynctask" >
  <uses-permission android:name="android.permission.INTERNET"/>
  <application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme" >
    <activity
      android:name=".MainActivity"
      android:label="@string/app_name" >
      <intent-filter>
        <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
      </activity>
  </application>

</manifest>
Listagem 3. Android Manifest

O código responsável por baixar a imagem não tem segredo nenhum e pode ser visto na Listagem 4. Esse código está contido dentro de uma classe Auxiliar.

package br.com.home.asynctask;
 
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
 
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
 
/**
 * Created by allanromanato on 9/30/15.
 */
public class Auxiliar {
  public static Bitmap baixarImagem(String url) throws IOException{
    URL endereco;
    InputStream inputStream;
    Bitmap imagem;

    endereco = new URL(url);
    inputStream = endereco.openStream();
    imagem = BitmapFactory.decodeStream(inputStream);

    inputStream.close();

    return imagem;
  }
}
Listagem 1. NOME

Por conter um método estático, essa classe não precisa ser instanciada para acessar o método baixarImagem. Aqui é definida a URL que contém a imagem e seu endereço é passado por parâmetro. Um objeto do tipo InputStream é responsável por receber as informações da imagem, que por enquanto ainda está codificada. Por fim, o objeto do tipo Bitmap recebe o retorno do método estático decodeStream contido dentro da classe BitmapFactory, que é responsável por converter para Bitmap o que estava armazenado dentro do inputStream. Este último tem sua conexão encerrada e a imagem é retornada.

Veremos a seguir como montar o código principal da nossa aplicação começando pelo código da Listagem 5.

 @Override
    protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      imagem = (ImageView)findViewById(R.id.imageView);
      baixar = (Button)findViewById(R.id.button);
      endereco = (EditText)findViewById(R.id.editText);

      Log.i("AsyncTask", "Elementos de tela criados e atribuidos Thread: " + 
      Thread.currentThread().getName());
      baixar.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            Log.i("AsyncTask", "Botão Clicado Thread: " + 
            Thread.currentThread().getName());
            chamarAsyncTask(endereco.getText().toString());
        }
      });
    }
 
    private void chamarAsyncTask(String endereco){
        TarefaDownload download = new TarefaDownload();
        Log.i("AsyncTask", "AsyncTask senado chamado Thread: " + 
        Thread.currentThread().getName());
        download.execute(endereco);
    }
Listagem 5. Primeira parte do código

O básico de qualquer aplicação Android pode ser observado nesse código, onde os objetos do tipo ImageView, Button e EditText tem seus respectivos representantes no XML atrelado. No botão é ligado tem um onClickListner, que identificará quando ele for pressionado para executará o conteúdo do método onClick. Nesse caso tem-se a chamada ao método chamarAsyncTask, passando por parâmetro o conteúdo digitado dentro do EditText. Ao chamá-lo, este instancia um objeto do tipo TarefaDownload.

É muito importante entender que nunca se deve chamar os métodos da AsyncTask separadamente, pois eles não funcionarão: para o correto funcionamento deve-se utilizar o objeto da classe estendida à AsyncTask e chamar o método para que execute, passando os paramentos que são necessários, garantindo assim o correto funcionamento.

Notem também que cada parte do código tem um Log.i para observar no log do Android onde está sendo executada cada tarefa.

Na Listagem 6 pode-se observar a classe que estende de AsyncTask.

private class TarefaDownload extends AsyncTask<String, Void, Bitmap>{
  @Override
  protected void onPreExecute(){
    Log.i("AsyncTask", "Exibindo ProgressDialog na tela Thread: " + 
    Thread.currentThread().getName());
    load = ProgressDialog.show(MainActivity.this, "Por favor Aguarde ...",
            "Baixando Imagem ...");
  }

  @Override
  protected Bitmap doInBackground(String... params) {
    Bitmap imagemBitmap = null;
    try{
        Log.i("AsyncTask", "Baixando a imagem Thread: " + Thread.currentThread().getName());
        imagemBitmap = Auxiliar.baixarImagem(params[0]);
    }catch (IOException e){
        Log.i("AsyncTask", e.getMessage());
    }

    return imagemBitmap;
  }

  @Override
  protected void onPostExecute(Bitmap bitmap){
    if(bitmap!=null) {
        imagem.setImageBitmap(bitmap);
        Log.i("AsyncTask", "Exibindo Bitmap Thread: " + Thread.currentThread().getName());
    }else{
        Log.i("AsyncTask", "Erro ao baixar a imagem " + Thread.currentThread().getName());
    }
    Log.i("AsyncTask", "Tirando ProgressDialog da tela Thread: " + 
    Thread.currentThread().getName());
    load.dismiss();
  }
}
Listagem 6. Classe privada responsável por executar o paralelismo

Dentro da classe TarefaDownload pode-se notar todos os métodos comentados anteriormente:

  • O onPreExecute está habilitando um ProgressDialog, responsável por dar um feedback ao usuário, mostrando que a imagem está sendo baixada;
  • O método doInBackground está chamando o método estático auxiliar responsável por baixar a imagem e armazená-la em um objeto do tipo Bitmap. Como parâmetro é passada a posição 0 (primeira posição) do vetor de Strings que está na assinatura do mesmo.
  • O método onPostExecute checa se o Bitmap recebido (retorno do método doInBackground) é nulo: se não for, faz a imagem ser exibida no ImageView anteriormente programado. No final desse método, o ProgressDialog é retirado da tela.

Notem que em todos os Logs tem uma chamada Thread.currentThread().getName(): isso é utilizado para mostrar no log em qual Thread cada parte do código está executando. Na Listagem 7 pode-se ver o resultado soltado no Log.

10-01 22:33:47.031    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
Elementos de tela criados e atribuidos Thread: main
10-01 22:34:36.865    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
Botão Clicado Thread: main
10-01 22:34:36.869    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
AsyncTask senado chamado Thread: main
10-01 22:34:36.869    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
Exibindo ProgressDialog na tela Thread: main
10-01 22:34:36.893    4598-4618/br.com.home.asynctask I/AsyncTask﹕ 
Baixando a imagem Thread: AsyncTask #1
10-01 22:34:38.470    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
Exibindo Bitmap Thread: main
10-01 22:34:38.470    4598-4598/br.com.home.asynctask I/AsyncTask﹕ 
Tirando ProgressDialog da tela Thread: main
Listagem 7. Logs das threads

Observem que a maioria dos logs mostra o comando que foi executado na Thread main, com exceção do "Baixando Imagem", que executa dentro da Thread AsyncTask #1, ou seja, ele está de fato sendo executado fora da Thread principal, em uma linha de execução diferente.

A seguir, as Figuras 2, 3 e 4 mostram o resultado final dessa operação.

Tela inicial
Figura 2. Tela inicial
Aguardando
Figura 3. Aguardando
Resultado
final
Figura 4. Resultado final

A Figura 2 mostra a tela antes de ser inserida a URL para capturar a imagem, enquanto a Figura 3 mostra a tela enquanto o usuário está aguardando a finalização do download da mesma. Por fim, a Figura 4 mostra o resultado final para o usuário.

Na Listagem 8 temos o código completo para quem perdeu alguns passos da criação da classe AsyncTask, responsável pelo PseudoParalelismo.

package br.com.home.asynctask;
 
import android.app.Activity;
import android.app.ProgressDialog;
import android.graphics.Bitmap;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
 
import java.io.IOException;
 
public class MainActivity extends Activity {
  private ImageView imagem;
  private Button baixar;
  private EditText endereco;
  private ProgressDialog load;

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    imagem = (ImageView)findViewById(R.id.imageView);
    baixar = (Button)findViewById(R.id.button);
    endereco = (EditText)findViewById(R.id.editText);

    Log.i("AsyncTask", "Elementos de tela criados e atribuidos Thread: " + 
    Thread.currentThread().getName());
    baixar.setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
          Log.i("AsyncTask", "Botão Clicado Thread: " + Thread.currentThread().getName());
          chamarAsyncTask(endereco.getText().toString());
      }
    });
  }

  private void chamarAsyncTask(String endereco){
      TarefaDownload download = new TarefaDownload();
      Log.i("AsyncTask", "AsyncTask senado chamado Thread: " + 
      Thread.currentThread().getName());
      download.execute(endereco);
  }
 
  private class TarefaDownload extends 1AsyncTask<String, Void, Bitmap>{
      @Override
      protected void onPreExecute(){
          Log.i("AsyncTask", "Exibindo ProgressDialog na tela Thread: " + 
          Thread.currentThread().getName());
          load = ProgressDialog.show(MainActivity.this, "Por favor Aguarde ...",
                  "Baixando Imagem ...");
      }
 
    @Override
    protected Bitmap doInBackground(String... params) {
        Bitmap imagemBitmap = null;
        try{
            Log.i("AsyncTask", "Baixando a imagem Thread: " + 
            Thread.currentThread().getName());
            imagemBitmap = Auxiliar.baixarImagem(params[0]);
        }catch (IOException e){
            Log.i("AsyncTask", e.getMessage());
        }

        return imagemBitmap;
    }
 
    @Override
    protected void onPostExecute(Bitmap bitmap){
        if(bitmap!=null) {
            imagem.setImageBitmap(bitmap);
            Log.i("AsyncTask", "Exibindo Bitmap Thread: " + 
            Thread.currentThread().getName());
        }else{
            Log.i("AsyncTask", "Erro ao baixar a imagem " + 
            Thread.currentThread().getName());
        }
        Log.i("AsyncTask", "Tirando ProgressDialog da tela Thread: " + 
        Thread.currentThread().getName());
        load.dismiss();
    }
  }
   
}
Listagem 8. Código principal

Como pode ser visto, a AsyncTask permite o gerenciamento e a criação de threads para a execução concorrente de aplicações sem muita complexidade que o assunto originalmente tem. Vale lembrar que é necessário pelo menos ter conhecimento prévio do funcionamento e gerenciamento de threads.