Uma das coisas mais comuns em aplicações móveis é o acesso a servidores web através do protocolo HTTP. O Android obviamente nos permite esse tipo de comunicação de várias formas. Mas uma forma interessante foi introduzida na versão 3 da plataforma: os Loaders. Esse tipo de abordagem permite isolar a Activity da classe que está fazendo o download dos dados.
Um exemplo típico dessa abordagem é a utilização da classe AsyncTask - que eu sempre usei, e ainda uso - jutamente com um ProgressDialog. Normalmente o ProgressDialog era um atributo da AsyncTask e no onPreExecute ele era exibido, sendo ocultado no onPostExecute. O problema dessa abordagem é que ao rotacionar a tela do aparelho, a Activity é recriada. Dessa forma, quando a AsyncTask terminava seu trabalho, a instância do Dialog armazenada ficava apontando para a instância da Activity que o tinha criado, gerando uma exceção em tempo de execução.
Com a chegada dos Fragments, é possível reter a instância do mesmo e ter um controle melhor sobre a rotação da tela (e outras mudanças de configuração). Mas a abordagem que eu achei interessante é a utilização da classe AsyncTaskLoader em conjunto com o LoadManager e a interface LoaderCallbacks. Vejamos o exemplo abaixo:
public class MainActivity extends FragmentActivity implements LoaderCallbacks<String> { ProgressDialog dialog; String json; private static final int MEU_LOADER = 0; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Recuperando o estado da Activity if (savedInstanceState != null) { json = savedInstanceState.getString("valores"); atualizaTela(); } // Essa classe gerencia todos os Loaders LoaderManager lm = getSupportLoaderManager(); // Se o JSON ainda não foi baixado if (json == null) { // Exibe o dialog dialog = ProgressDialog.show( this, "Aguarde...", "Baixando tweets"); // E inicializa o Loader (se ele já foi // inicializado, ele apenas continuará) lm.initLoader(MEU_LOADER, null, this); } } @Override protected void onDestroy() { super.onDestroy(); // Se estiver exibindo o progress, remova-o if (dialog != null && dialog.isShowing()) dialog.dismiss(); } @Override protected void onSaveInstanceState(Bundle outState){ super.onSaveInstanceState(outState); // Salvando o estado da Activity outState.putString("valores", json); } // Métodos da interface LoaderCallbacks ----------- @Override public Loader<String> onCreateLoader( int id, Bundle args) { // Instancia e retorna a AsyncTaskLoader return new MinhaAsyncTask(this); } @Override public void onLoadFinished(Loader<String> loader, String dados) { // Chamado quando o Loader termina seu trabalho // Aqui, atribuímos o resultado ao // atributo json e ocultamos o dialog json = dados; atualizaTela(); dialog.dismiss(); } @Override public void onLoaderReset(Loader<String> loader) { // Chamado se o Loader for resetado Log.d("NGVL", "onLoaderReset"); } // AsyncTaskLoader public static class MinhaAsyncTask extends AsyncTaskLoader<String> { // O JSON baixado será armazenado aqui para ser // repassado para Activity String data; public MinhaAsyncTask(Context context) { super(context); } @Override protected void onStartLoading() { // Se não baixou os dados, faça agora! if (data == null) { forceLoad(); // Se já baixou, apenas entregue o resultado } else { deliverResult(data); } } @Override public String loadInBackground() { // Nesse método será feito o download do JSON return downloadJSON(); } } }O código acima está todo comentado. Então vou focar nos detalhes que devem ser observados.
O primeiro ponto é utilizar as classes do pacote android.support.v4.* pois o conceito de Loaders só surgiu no Honeycomb, então utilizei aqui a API de compatibilidade para que esse código execute sem problemas nas versões anteriores ao Android 3. O segundo ponto interessante é que, com essa abordagem o download não é reiniciado quando a Activity é recriada.
Outro detalhe é que, caso você queira chamar o loader novamente, você deve usar o método restartLoader e não o initLoader. E se quiser passar parâmetros para o Loader, pode usar um objeto Bundle e passá-lo como o segundo argumento desses métodos.
getSupportLoaderManager().restartLoader( MEU_LOADER, null, this);Os métodos downloadJSON() e atualizaTela() devem ser implementados por você :) para baixar o JSON desejado e atualizar a tela da maneira apropriada.
Abaixo eu coloquei um exemplo desses dois métodos que baixa o JSON de uma busca no Twitter e exibe os textos dos Tweets em uma ListView.
private void atualizaLista() { if (json == null) return; try { JSONObject jsonObj = new JSONObject(json); JSONArray jsonResults = jsonObj.getJSONArray("results"); List<String> texts = new ArrayList<String>(); for (int i = 0; i < jsonResults.length(); i++) { JSONObject result = jsonResults.getJSONObject(i); texts.add(result.getString("text")); } ListView list = (ListView) findViewById(R.id.ListView1); list.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, texts)); } catch (JSONException e) { e.printStackTrace(); } } private static String downloadJSON() { try { URL url = new URL( "http://search.twitter.com/search.json?q=jctransito"); HttpURLConnection conexao = (HttpURLConnection) url.openConnection(); conexao.connect(); InputStream is = conexao.getInputStream(); BufferedReader reader = new BufferedReader( new InputStreamReader(is)); String s = reader.readLine(); return s; } catch (Exception e) { e.printStackTrace(); } return null; }Não esqueça de adicionar a permissão de INTERNET no AndroidManifest.xml.
Com abordagem acima, mesmo ao girar o aparelho, o download continuará, e caso não tenha terminado quando a Activity carregar, o ProgressDialog será exibido novamente.
Qualquer dúvida, deixem seus comentários.
4br4ç05,
nglauber
Referências:
http://developer.android.com/reference/android/app/LoaderManager.html
http://developer.android.com/reference/android/app/LoaderManager.LoaderCallbacks.html
http://developer.android.com/reference/android/content/AsyncTaskLoader.html
5 comentários:
Como parar a execução da "AsyncTaskLoader" pressionando o botão voltar (KEYCODE_BACK KeyDown), pois gostaria de deixar o usuário decidir se ele quer ou não aguardar esse processo ?
Mas foi muito Interessante o seu post e resolve meus problemas ao realizar download de dado. Parabéns!
Saberia me informar, se dentro de um processo lento de conexão com o servidor
aguardando a resolução de um SQL (Tipo 1 minuto).
O AsyncTaskLoader resolveria essa situação ?
Pois venho a tempo tentando uma Thread Ilimitada para executar procedimentos em Backgound.
Até então o AsyncTaskLoader me pareceu ser a melhor solução mas ele não aguenta 10 segundos e estoura o ANR.
Se puder me ajudar, ficarei agradecido, pois estou ficando muito louco com isso.
Obrigado !
Muito útil a dica do "AsyncTaskLoader". Certeza que vou usar, Inclusive, em versões do sdk maior que 9. O Android nem permite que você espere uma resposta de um get sem estar em uma thread. Levanta a exceção:
“Network OnMainThreadExveption”
Para permitir que a aplicação espere a resposta tem que da permissão com “ThreadPolicy.”. Claro que não é uma boa pratica.
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
Olá, primeiramente parabéns pelo blog , é o melhor conteúdo em português que eu achei até agora. Gostaria de questioná-lo a respeito da leitura de json , estou lendo um json um pouco grande de mais ou menos 6000 mil registros de um bd mysql, e a vezes tenho tido problema com uma excessão de memória, existe alguma técnica específica para evitar esse problema, qual a melhor maneira de se fazer isso , desde já muito obrigado.
Oi Marcelo,
Obrigado pelos elogios :)
A técnica mais adotada é a paginação. Na primeira solicitação, seu JSON retorna 100 registros (por exemplo), e além dessa lista, um token para a próxima página.
Da segunda requisição em diante, você pegará o token da requisição anterior e fará uma nova requisição.
É assim que Google, Fabebook, Twitter fazem para retornar seus dados...
Uma outra sugestão é zipar seu JSON no servidor e dezipar no cliente. Diminuindo o tempo e a quantidade de dados lidos da rede.
4br4ç05,
nglauber
Postar um comentário