segunda-feira, 23 de fevereiro de 2015

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.

Admin: Bruno

Olá Galera! muito grato por estarem acessando nosso blog. Espero que seja possível transmitir de forma compreensível um pouco de meus conhecimentos em programação, para esta comunidade de desenvolvedores que cresce cada vez mais! Espero que Gostem! Abraço! E meu enorme obrigado à Renato Simões, Átila Soares,Wanderson Quinto, Emerson e a toda galera que sempre ajudou meu sincero obrigado....
Especialmente a Natalia Failache e Rita de Cassia que sempre apoiaram este sonho....

De seu amigo Bruno Rafael.