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.
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>
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>
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;
}
}
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){
}
}
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]));
}
}
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);
}
}
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.
- Documentação de Referência Android, disponível em Android Developers.