Threads separadas em Android

Quem de nós nunca se deparou com o seguinte diálogo ao executar uma aplicação Android?

ANR - application not responding

Mas o que causa esse problema?

Este é um diálogo mostrado pelo sistema quando ao identificar que a aplicação está executando muitas operações na main thread – a thread principal ou UI thread.

A UI thread é responsável pelas interações com o usuário.  Por isso, quando o sistema percebe que ela está fazendo muito processamento, ele considera que a aplicação não está respondendo ao usuário e, portanto, está travada. Neste momento ele dá a opção de fechar a aplicação – o inconveniente diálogo application not responding – ou simplesmente ANR.

Como evitar um ANR

Mas como evitar um ANR? Simples! Basta executar as operações demoradas (como acesso a rede, acesso a banco de dados, entre outros) de sua aplicação em uma thread separada. E para fazer isso existem três mecanismos:

  • Threads (Java nativo)
  • Asynctasks
  • Handlers

O primeiro mecanismo (threads nativas de java) funciona, porém não é recomendado em Android – principalmente porque as outras implementações permitem que uma parte da execução seja feita na UI thread, o que geralmente é necessário. Os Handlers, por sua vez, são ferramentas poderosas e serão tratados em um próximo capítulo. Neste capítulo estudaremos as AsyncTasks.

AsyncTasks

As AsyncTasks são classes do framework capazes de executar código em uma thread separada, quando este é colocado dentro de seu método doInBackground.

Para implementar uma AsyncTask, basta criar uma subclasse desta e fazer override dos métodos necessários. Os principais métodos desta classe são:

  • doInBackground: como já dito, esse é o método responsável por executar código em segundo plano (em uma thread separada)
  • onPreExecute: este método é executado assim que a AsyncTask inicia sua execução, antes do método doInBackground. Ele é executado na UI thread.
  • onPostExecute: assim como o onPreExecute, este método é executado na UI thread, porém depois do método doInBackground.
  • onProgressUpdate: este método é invocado quando é alterado o progresso. (O progresso pode ser alterado a qualquer momento durante a execução de doInBackground, através do método setProgress) Este mecanismo é geralmente usado para a criação de barras de progresso.

No nosso projeto QuickNotes, utilizado neste curso, temos um problema evidente na MainActivity: o método insertNote acessa o banco de dados (uma operação que pode ser demorada). Iremos resolver este problema implementando uma AsyncTask, da seguinte forma:

 

	 private class AddNoteTask extends AsyncTask<String, Void, Uri> {
		 @Override
	     protected Uri doInBackground(String... string) {
	 		ContentValues values = new ContentValues();
			values.put(QuickNotesProvider.Notes.TEXT, string[0]);

			return getContentResolver().insert(
					QuickNotesProvider.Notes.CONTENT_URI, values);
	     }
		 
		 @Override
	     protected void onPostExecute(Uri result) {
			 // Após a inserção da nota, vamos mostrar um Toast ao usuário
			 Toast toast = Toast.makeText(MainActivity.this, 
					 "Nota inserida com sucesso!", 
					 Toast.LENGTH_SHORT);
			 toast.show();
	     }
	 }

Executando a AsyncTask

Com a AsyncTask criada basta executá-la no método addNote, que agora irá lançar a AsyncTask ao invés de inserir diretamente no banco de dados.

     /*
      * Método responsável por inserir um registro no content provider
      */
	 protected void addNote(String text) {
		 AddNoteTask task = new AddNoteTask();
		 // É necessário passar como parâmetro um array de strings,
		 // que será o parâmetro recebido pelo método doInBackground.
		 // Neste caso, será passado um array com apenas um item: A nova nota
		 task.execute(new String[] { text } );
	 }

Por fim, a MainActivity fica assim:

package br.com.felipesilveira.quicknotes;

import android.app.ListActivity;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;

public class MainActivity extends ListActivity {
	
	private static final String TAG = "QuickNotesMainActivity";
	private Cursor mCursor;
	
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        Log.d(TAG, "Criando a MainActivity");
      
        setContentView(R.layout.main);
        
        Intent i = new Intent(this, WelcomeActivity.class);
        startActivity(i);
        
        Button insertButton = (Button)findViewById(R.id.insert_button);
        insertButton.setOnClickListener(mInsertListener);

        // adicionando um 'Hint' ao Editbox.
        EditText editBox = (EditText)findViewById(R.id.edit_box);
        editBox.setHint("Nova nota...");
        
        mCursor = this.getContentResolver().
                  query(QuickNotesProvider.Notes.CONTENT_URI, null, null, null, null);

        ListAdapter adapter = new SimpleCursorAdapter(
        		              // O primeiro parametro é o context.
        		              this, 
        		              // O segundo, o layout de cada item. 
        		              R.layout.list_item,
        		              // O terceiro parametro eh o cursor que contem os dados
        		              // a serem mostrados
        		              mCursor,
        		              // o quarto parametro eh um array com as colunas do cursor
        		              // que serao mostradas
        		              new String[] {QuickNotesProvider.Notes.TEXT},
        		              // o quinto parametro é um array (com o mesmo tamanho
        		              // do anterior) com os elementos que receberao
        		              // os dados.
        		              new int[] {R.id.text});
 
        setListAdapter(adapter);
     }
     
     /*
      *  Definindo um OnClickListener para o botão "Inserir"
      */
     private OnClickListener mInsertListener = new OnClickListener() {
          public void onClick(View v) {
        	  EditText editBox = (EditText)findViewById(R.id.edit_box);
              addNote(editBox.getText().toString());
              editBox.setText("");
          }
     };
     
     /*
      * Método responsável por inserir um registro no content provider
      */
	 protected void addNote(String text) {
		 AddNoteTask task = new AddNoteTask();
		 // É necessário passar como parâmetro um array de strings,
		 // que será o parâmetro recebido pelo método doInBackground.
		 // Neste caso, será passado um array com apenas um item: A nova nota
		 task.execute(new String[] { text } );
	 }
	 
	 private class AddNoteTask extends AsyncTask<String, Void, Uri> {
		 @Override
	     protected Uri doInBackground(String... string) {
	 		ContentValues values = new ContentValues();
			values.put(QuickNotesProvider.Notes.TEXT, string[0]);

			return getContentResolver().insert(
					QuickNotesProvider.Notes.CONTENT_URI, values);
	     }
		 
		 @Override
	     protected void onPostExecute(Uri result) {
			 // Após a inserção da nota, vamos mostrar um Toast ao usuário
			 Toast toast = Toast.makeText(MainActivity.this, 
					 "Nota inserida com sucesso!", 
					 Toast.LENGTH_SHORT);
			 toast.show();
	     }
	 }
}

Para fazer download do projeto inteiro, clique aqui.

Desenvolvendo para Android

Deixe um comentário