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