Utilizando Threads de forma correta no Android: o Famoso erro: NetworkOnMainThreadException


Eu não sou friboi mas tô na moda! e a mulherada gosta! a mulherada gosta é do papai...

O condenado à morte esperava a hora da execução, quando chegou o padre: Meu filho, vim trazer a palavra de Deus para você. Perda de tempo, seu padre. Daqui a pouco vou falar com Ele, pessoalmente. Algum recado?

¬¬ '' ¬¬'' kkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkkk! Eu curti :p

Fala cambada de sofredores! Você menino maroto e faceiro desenvolvedor mobile está lá desenvolvendo seu app usando umvirtual device com android 2.1 e tudo funciona maravilhosamente, você se acha o cara, já pensa em dominar o mundo desenvolvendo um aplicativo de alto nível que será vendido por um preço baixo, mas, que terá milhões de downloads e lhe renderá uma boa grana e visibilidade, fazendo o seu projeto ser comprado por um gigante como google ou facebook, quando de repente você vai testar seu app em um virtual device com android honeycomb ou superior e então…
log_networkonmainthread
Por que ocorre essa exceção?
Essa exceção ocorre a partir da API 11 ou versão 3.0 do android, como o próprio nome já diz, o motivo da exceção ocorrer é a aplicação estar efetuando algum processo de network na thread principal (daí o nome MainThread), nas versões anteriores essa operação é permitida apesar de não ser recomendada, pois dependendo do tempo que o processo demora, pode ocorrer um ANR (Application Not Responding).
O ANR geralmente ocorre quando a aplicação não responde aos comandos do usuário ou um BroadcastReceiver  demora mais que 10 segundos, portanto ao fazer uma operação demorada nathread principal da aplicação que é a mesma thread da UI (User Interface ou simplesmente tela) sua aplicação pode travar, a partir da versão 3.0 do android foi implementada essa “proteção” no sistema, justamente para forçar o desenvolvedor a tomar um atitude quanto a essa questão, no Android a responsividade da aplicação é monitorada pelo Activity Manager e o Window Manager System Services.
Vamos à 3 possíveis soluções (Duas Pogs e uma que preste :p) para esse problema, uma ruim, uma razoável e uma ideal, abaixo vou descrevê-las.

SOLUÇÃO RUIM GAMBI NÍVEL 10

A solução ruim é chamada dessa forma por não ser a solução recomendada para tratar o caso, geralmente é usada como paliativo para poder continuar o desenvolvimento da aplicação, porém, o correto é ser trocada pela solução ideal o quanto antes, para aplicar essa solução devemos usar o StrictMode para permitir que seja possível efetuar uma operação de rede na thread principal, o trecho de código abaixo faz a maldade.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package br.com.vandersonguidi.aplicacaoteste;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
 
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
 
import android.app.Activity;
import android.os.Bundle;
import android.os.StrictMode;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
 
public class MainActivity extends Activity {
    public EditText etResultado;
    public Button btnProcessar;
    public TextView tvStatus;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder()
                .permitAll().build();
        StrictMode.setThreadPolicy(policy);
 
        setContentView(R.layout.activity_main);
        btnProcessar = (Button) findViewById(R.id.btnProcessar);
        etResultado = (EditText) findViewById(R.id.etResultado);
        tvStatus = (TextView) findViewById(R.id.tvStatus);
 
        btnProcessar.setOnClickListener(new View.OnClickListener() {
 
            @Override
            public void onClick(View v) {
                String URL = "http://vandersonguidi.com.br/networkonmainthread.txt";
 
                try {
 
                    HttpClient client = new DefaultHttpClient();
                    HttpGet requisicao = new HttpGet();
                    requisicao.setHeader("Content-Type",
                            "text/plain; charset=utf-8");
                    requisicao.setURI(new URI(URL));
                    HttpResponse resposta = client.execute(requisicao);
                    BufferedReader br = new BufferedReader(
                            new InputStreamReader(resposta.getEntity()
                                    .getContent()));
                    StringBuffer sb = new StringBuffer("");
                    String linha = "";
 
                    while ((linha = br.readLine()) != null) {
                        sb.append(linha);
                    }
 
                    br.close();
 
                    linha = sb.toString();
 
                    Toast.makeText(getApplicationContext(), linha,
                            Toast.LENGTH_LONG).show();
                    return;
 
                } catch (Exception ex) {
                        tvStatus.setText(ex.getMessage());
                }
 
            }
        });
    }
 
    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.main, menu);
        return true;
    }
 
}
Na prática estamos alterando a política de threads para a aplicação, porém, conforme já dito, essa não é a solução ideal, evite ao máximo utiliza-la.

SOLUÇÃO RAZOÁVEL GAMBI MEIOTE NÍVEL 5

A solução razoável é chamada dessa forma pois apesar de ser um solução melhor do que a solução ruim, não é a solução recomendada na documentação da SDK do android.
o trecho de código abaixo faz a maldade
1
2
3
4
5
6
7
new Thread(new Runnable()
 
{
public void run() {
//faça qualquer coisa aqui
 }
 }).start();
Na prática estamos criando uma nova instância de uma thread e  dentro dessa threadinstanciamos um Runnable para efetuar uma operação, do lado de fora da thread chamamos o método start para executar a nossa operação.

SOLUÇÃO IDEAL FILÉ SEGUINDO ORIENTAÇÕES DA DOCUMENTAÇÃO (tE METE!)

A solução ideal é chamada desta forma pois é a forma indicada pela comunidade android, para implementar esta solução devemos implementar uma herança da classe AsyncTask, essa classe possibilita efetuar operações no backgroundda aplicação sem a necessidade de manipular Threads ou Handlers e obviamente sem sacrificar a thread principal da aplicação.
A classe AsyncTask tem 3 parâmetros genéricos:
o primeiro parâmetro é um array de parâmetros que você deseja passar para a aplicação (geralmente alguma informação que você necessite para executar a ação, ex: para consultar um URL você pode passar uma lista de URL’s ou de Strings).
o segundo parâmetro é uma unidade de progresso, caso você queira enviar uma resposta para sua aplicação (ex: efetuando o download de um arquivo, é possivel informar para a aplicação o status do download ou atualizar um ProgressDialog).
O terceiro parâmetro é o tipo que a classe vai retornar, ex: uma string, que pode conter processo executado com sucesso ou processo executado com erro, ou uma classe que vai ser passada vazia pra aplicação e vai ser populada dentro do processo.
então supondo que eu vá criar uma classe que vá efetuar uma consulta em uma url e retornar para a minha aplicação o resultado dessa consulta, vou chamar essa classe simplesmente de Consulta, a declaração ficaria assim:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
package br.com.vandersonguidi.aplicacaoteste;
 
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URI;
 
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
 
import android.os.AsyncTask;
 
public class Consulta extends AsyncTask<String, Void, Boolean> {
 
    @Override
    protected Boolean doInBackground(String... params) {
 
        String linha = "";
        Boolean Erro = true;
 
        if (params.length > 0)
            // faço qualquer coisa com os parâmetros
 
            try {
 
                HttpClient client = new DefaultHttpClient();
                HttpGet requisicao = new HttpGet();
                requisicao.setHeader("Content-Type",
                        "text/plain; charset=utf-8");
                requisicao.setURI(new URI(URL));
                HttpResponse resposta = client.execute(requisicao);
                BufferedReader br = new BufferedReader(new InputStreamReader(
                        resposta.getEntity().getContent()));
                StringBuffer sb = new StringBuffer("");
 
                while ((linha = br.readLine()) != null) {
                    sb.append(linha);
                }
 
                br.close();
 
                linha = sb.toString();
                Erro = false;
 
            } catch (Exception e) {
                Erro = true;
            }
 
        return Erro;
    }
}
E para chamar o método eu simplesmente uso o seguinte código:
1
new Consulta().execute(seuparametro);
Lembrando que esse código simplesmente vai executar o processo de background, caso você queira receber o resultado, que no nosso caso é um booleano indicando se ocorreu erro ou não, você pode usar a seguinte linha de código.
1
2
3
4
5
try {
  Boolean Resultado = new Consulta().execute(seuparametro).get();
} catch (Exception ex) {
     tvStatus.setText(ex.getMessage());
}
Bom, então é isso, não esqueçam que para efetuar qualquer requisição na internet é preciso adicionar a permissão
1
<uses-permission android:name="android.permission.INTERNET"></uses-permission>
Essa que é a bagaça! Se gostou beleza, se não gostou beleza também :p Fui!
Bruno Rafael.

Comentários

Postagens mais visitadas deste blog

Algorítimo Para Validar Cpf Segundo Receita Federal em Java

Executar Audio em Java Swing

Gerenciamento de projetos: Introdução, Experiência e Estudo - Parte I