Um dos grandes desafios dos programadores individuais e mesmo de empresas que produzem aplicações nativas da plataforma Android, é a administração da concorrência dentro destas aplicações. Os usuários estão cansados de reclamar de aplicações que são lentas, que consomem muita memória, ou mesmo que travam durante a execução de alguma tarefa relevante para o bom funcionamento da aplicação.

É muito importante ressaltar que um aplicativo desenvolvido com problemas de concorrência, ou mesmo com um mal gerenciamento da mesma, é um investimento ruim, pois se ele é avaliado negativamente no Google Play, ou se surgem comentários ruins sobre ele em outro lugar da internet assim que ele é lançado, mesmo ocorrendo novas atualizações, as quais sanem os problemas de concorrência, muitos potenciais usuários deixaram de baixá-lo. Em muitos casos, podemos dizer que o aplicativo morreu assim que nasceu.

Trabalhar na administração, criação e no gerenciamento de novas threads no desenvolvimento de aplicações Android não é uma tarefa trivial, no entanto a Classe AsyncTask permite que você consiga gerenciar esse processamento de forma que a complexidade do gerenciamento “manual“ não seja exposta ao programador. Vale lembrar que o programador de aplicações Android necessita obrigatoriamente conhecer o funcionamento do gerenciamento de threads.

Através de uma aplicação relativamente simples, você aprenderá como utilizar a classe AsyncTask para fazer a comunicação de uma thread qualquer com a UI thread.

Você deve estar se perguntando como isso é útil de forma prática? A UI thread é a única thread que pode modificar a interface gráfica e a principal thread de sua aplicação. A grande questão é, se você colocar todo o fluxo de processamento na thread principal, em algum momento a sua aplicação vai se tornar lenta ou travar. É claro que na prática ninguém faz isso. O exemplo demonstrará como realizar um processamento mais oneroso (no caso um contador) para a aplicação em uma thread. O resultado desse processamento será exibido na tela a cada ciclo, através da transferência de responsabilidade da thread que fez esse processamento, para a UI thread, a qual é a única thread que pode atualizar a interface gráfica.

Primeiro vamos criar um projeto com as configurações presentes nas Figuras 1 a 5.

Nota: No passo 2 os campos foram deixados em branco para sinalizar que você pode definir as nomenclaturas que quiser. No entanto, as que foram usadas são as seguintes: nome da aplicação=Ay, nome do projeto=Ay, nome do pacote=com.as.
Novo projeto
Figura 1. Passo 1 - Novo projeto
Nova aplicação android
Figura 2. Passo 2 - Nova aplicação android
Criando projeto
Figura 3. Passo 3 – Criando projeto
Criando atividade
Figura 4. Passo 4 – Criando atividade
Definindo atividades
Figura 5. Passo 5 – Definindo atividades

Com o projeto criado, vamos fazer algumas alterações para o devido funcionamento da nossa aplicação, são elas:

  • Alteração do arquivo strings.xml no diretório res/values;
  • Alterar o arquivo activity_as.xml no diretório res/layout;
  • Alterar a classe responsável por manipular o arquivo activity_as.xml, o qual se encontra no package que foi criado no momento da criação do projeto e se chama AsActivity;
  • Criar a classe que vai fazer o processamento e depois chamar a UI thread.

Primeiro vamos alterar a codificação do arquivo strings.xml para ele ficar igual ao seguinte. Veja a Listagem 1.


<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">Ay</string>
    <string name="action_settings">Settings</string>
    <string name="start">Processar..</string>
</resources>
Listagem 1. Exemplo de como o arquivo strings.xml ficará

A listagem acima “não tem tanta relevância pro funcionamento do aplicativo”. O arquivo strings.xml serve para mapear os elementos da tela e facilitar sua manutenção. Eles são os chamados resources.

Um exemplo é a tag string com o name=”start”, está mapeada para o elemento button na tela e está mapeada para o texto do botão.

Depois que o arquivo activity_as.xml for alterado ele vai ficar como a Listagem 2.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/LinearLayout1"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:paddingBottom="@dimen/activity_vertical_margin"
  android:paddingLeft="@dimen/activity_horizontal_margin"
  android:paddingRight="@dimen/activity_horizontal_margin"
  android:paddingTop="@dimen/activity_vertical_margin"
  tools:context=".AsActivity" >
 
   <Button
    android:id="@+id/bt"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="@string/start"
    android:onClick="processamento"
    android:layout_gravity="center"
   />
   <TextView
    android:id="@+id/tex"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:gravity="center"
    android:textSize="25pt"
    android:textStyle="bold"
    android:textColor="#00FF00"
  />
</LinearLayout>
Listagem 2. Exemplo de como o arquivo activity_as.xml vai ficar

A listagem acima cuida da disposição dos elementos na tela, bem como o layout e os elementos que vão dentro do layout, que no caso são um botão e um textview. O botão referência a tag string com o name start através do item android:text.

Existe também um item bastante importante no elemento button para o qual precisamos atentar: o android:onclick. Este item diz que quando o botão for clicado, um método com o valor dele será invocado. Também não podemos nos esquecer do item adroid:id, o qual será a nossa porta de entrada para o acesso a esses elementos mediante outras classes.

O aquivo AsActivity deve estar inicialmente como referenciado na Listagem 3.


package com.as;
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
 
public class AsActivity extends Activity {
 
  @Override
  protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_as);
  }
  @Override
  public boolean onCreateOptionsMenu(Menu menu) {
  //Inflate the menu; this adds items to the action 
  //bar if it is present.
     getMenuInflater().inflate(R.menu.as, menu);
     return true;
  }
}
Listagem 3. Exemplo de como o arquivo deve estar inicialmente

Após sua alteração, a classe deve estar como a Listagem 4.


package com.as;
 
import android.os.Bundle;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
 
public class AsActivity extends Activity {
 
  private TextView tex;
  private Button bt;
  
  @Override
  protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_as);
     
     //Recupera o textView da activity 
     //activity_as referenciada acima
     //pelo setContentView.
     tex = (TextView) findViewById(R.id.tex);
     
     //Recupera o Button view da activity 
     //activity_as referenciada acima                 
    //pelo setContentView.
     bt = (Button) findViewById(R.id.bt);
                 
  }
   public void processamento(View view){
                 
  }
}
Listagem 4. Exemplo de como a classe vai ficar

No caso acima, o processo acontece da seguinte forma: quando a activity é iniciada, o método onCreate é chamado, esse por sua vez carrega a tela através do método setContentView, o qual recebe uma referência ao arquivo activity_as.xml (no caso é o arquivo que responsável pelos elementos da tela). Depois o botão e a textview são recuperadas através de uma chamada a findViewById para que esses itens possam ser manipulados. O nosso objetivo é usar essas referências no método processamento, o qual será invocado quando o botão for clicado, mas como este método ainda está vazio, falaremos dele mais a frente.

Agora só falta criar a classe Num, ela vai ser criada no pacote onde foi criada a classe que comanda a activity principal. Observe a Listagem 5.


package com.as;
 
import android.os.AsyncTask;
import android.widget.Button;
import android.widget.TextView;
 
public class Num extends AsyncTask<Integer, Integer, Void>{
 
  private TextView text;
  private Button bt;
  
  public Num(TextView text, Button bt) {
                 this.text = text;
                 this.bt = bt;
  }

  //É onde acontece o processamento
  //Este método é executado em uma thread a parte,
  //ou seja ele não pode atualizar a interface gráfica,
  //por isso ele chama o método onProgressUpdate,
  // o qual é executado pela
  //UI thread.
  @Override
  protected void doInBackground(Integer... params) {
     int n = params[0];
     int i = 0;
     while(i < n){
       try{    
         Thread.sleep(1000);
       }catch(InterruptedException e){
         e.printStackTrace();
       }
      //Notifica o Android de que ele precisa 
      //fazer atualizações na
      //tela e chama o método onProgressUpdate 
      //para fazer a atualização da interface gráfica 
      //passando o valor do 
      //contador como parâmetro.
     publishProgress(i);
     i++;
     }
     return null;
  }
  // É invocado para fazer uma atualização na
  // interface gráfica
  @Override
  protected void onProgressUpdate(Integer... values) {
                 text.setText(String.valueOf(values[0]));
  }              
}
Listagem 5. Exemplo de como a classe vai ficar

A classe AsyncTask tem como sua função prioritária executar código que demanda mais poder de processamento em um thread diferente da UI thread, e o melhor disso é que ele não põe você em contato direto com a complexidade da programação tradicional para a manipulação de threads. No caso acima, o comentário do código já adianta grande parte do trabalho, no entanto, é preciso dizer que pra utilizar a classe Asynctask você precisa estender ela passando alguns parâmetros na forma de generics. Generics é um conceito não particular da programação android e serve para parametrizar os tipos de dados passados. É recomendado que você já conhecesse o funcionamento deles, pois eles são bastante importantes em algumas situações da programação android.

Já os métodos funcionam da seguinte forma: o método doInBackground executa fora da UI thread e simula o processamento que queremos fazer através de um loop, o qual cria um contador. A cada ciclo de processamento desse contador, o método publishProgress(i) é invocado e o valor de i é passado. A função desse método é invocar o método de baixo, no caso o onProgressUpdate, o qual executa da UI thread e faz a atualização da interface gráfica.

Precisamos ainda acrescentar algumas linhas de código à classe que comanda a activity principal para que o projeto execute com sucesso. Veja a Listagem 6.

package com.as;
import android.os.Bundle;
import android.app.Activity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
 
public class AsActivity extends Activity {
       
   private TextView text;
   private Button bt; 
 
   @Override
   protected void onCreate(Bundle savedInstanceState) {
     super.onCreate(savedInstanceState);
     setContentView(R.layout.activity_as);
     
     text = (TextView) findViewById(R.id.texto);
     bt = (Button) findViewById(R.id.botao);
   }      
   public void processamento(View view){         
     Num num = new Num(text, bt);
     // Executa o doInBackground e passa o valor 50 como parâmetro
     num.execute(50);
   }
}
Listagem 6. Exemplo de como a classe vai ficar

O que vamos fizemos foi apenas colocar o código necessário para que possamos utilizar a classe Num a partir do click do botão. Neste caso vamos instancia-la e depois usar o método, passando o valor que queremos como parâmetro para a contagem dos ciclos.

Depois de terminado, você pode executar o projeto no emulador android e ver como a aplicação se comporta.

Bibliografia:

Confira também