quinta-feira, 12 de dezembro de 2013

Problemas comuns em Android

Olá à todos.

Neste tópico atenderei ao pedido de muitos desenvolvedores Android. Listarei 30 problemas comuns no desenvolvimento de aplicativos para Android.

1. ConvertView


 Ao utilizar ListView's, que são componentes visuais em Android que inflam listas, combo boxes, etc, é comum o usuário deixar de implementar a verificação de lógica do componente "View convertView", o que acarreta um processamento absurdamente maior para o Android montar o layout de cada elemento da lista.Em porcentagem, o aumento do processamento necessário para inflar o layout na lista se torna incomensurável. Por exemplo, em uma lista com 10 mil elementos, ao invés do layout de cada elemento ser inflado 1 vez (com a verificação do componente "View convertView"), ele será inflado 10 mil vezes. Isso dá um aumento de 10.100%.

Se a lista conter 1 milhão de elementos, o aumento será de 1.000.100%. Logo, incomensurável. 

2. ViewHolder


 Ao utilizar ListView's, é comum o usuário deixar de implementar utilizar a lógica do componente “ViewHolder”, o que acarreta um processamento 15% maior para o Android montar o layout de cada elemento da lista.

A boa prática indicada pelo Android, quando utilizamos ListView's, é de utilizar tanto o “convertView” quanto o “ViewHolder”.


3. Resoluções de telas em Android são fragmentadas


Em Android existe uma grande fragmentação. Ou seja, existe no mundo uma grande quantidade de dispositivos rodando em Android (mais de 7 mil), sejam celulares, tablets, smartwatches, dentre outros dispositivos.

O problema que isto acarreta é que existem diversos tamanhos de tela. Logo, se criarmos um aplicativo que rode fluidamente, por exemplo, em um Samsung Galaxy S4, não há garantias que sua interface rode fluidamente em um Samsung Galaxy Y, cuja tela é menor.

Tendo isto em vista, a Google disponibilizou uma metologia através dos diretórios res/drawable-%dpi, onde % é um prefixo relativo à um tamanho de tela. Por exemplo, "l" de "low", "m" de "medium", "h" de "high" e "xh" de "extreme high". Assim, um erro comum é o programador/designer mobile testar o aplicativo que está desenvolvendo APENAS para um tipo de tela. Isto é bastante não-aconselhável, já que o método correto visa disponibilizar um aplicativo para todos os dispositivos Android possíveis.


4. Depuração USB


Um erro comum é tentar rodar uma aplicação do Eclipse no celular sem depuração USB. Muitos desenvolvedores se esquecem disso, e acabam se desesperando. Para colocarmos um celular em depuração USB, precisamos setar uma opção em nosso celular.

No sistema operacional Mac OS, esta depuração é feita de forma automática. Em sistemas Linux, ela é feita mudando-se um arquivo de configuração, tal qual escrito no link http://developer.android.com/tools/device.html

Já em sistemas Windows a depuração é um pouco mais complicada, pois cada celular precisa ter um driver específico instalado no Windows. Em alguns celulares, como os da Samsung, a instalação deste driver é feita ao conectarmos o celular com o computador (via cabo USB). Em outros, como os da Motorola e da Sony, precisamos instalar um driver específico, obtido do site da empresa específica.

Além deste driver do computador, também precisamos habilitar a opção "USB Debugging" no celular. Até o sistema Gingerbread, isto era feito clicando-se, em sequência, em: "Configurações', "Aplicativos", "Desenvolvimento", "Depuração USB".

Já a partir do sistema Ice Cream Sandwich, a opção "Desenvolvimento", nativamente, veio desabilitada. Para a habilitarmos, precisamos clicar em "Configurações", depois em "Sobre o telefone", e então clicar na opção "Número da versão" 3x, bem rápido. Então, será habilitada a opção "Opções de desenvolvedor" dentro da tela anterior de "Configurações".

Ao entrarmos na opção "Opções de desenvolvedor", veremos a opção "Depuração USB", pronta para ser habilitada. Assim, caso o celular tenha seu driver instalado no Windows e a opção "Depuração USB" setada, estaremos prontos para executar uma aplicação em modo de depuração USB.

5. Id de uma ListView é diferente do Id de um dado de Webservice ou do Banco de Dados


É comum em Android um desenvolvedor querer chamar outra tela, a partir de uma ListView. De fato, isto é muito fácil de ser implementado. Porém, é comum que, ao entrar na tela chamada, os dados não sejam exatamente o que o desenvolvedor imaginou. Isto acontece porque podemos ter Id’s tanto da ListView, quanto do Webservice, quanto do Banco de Dados.

Por exemplo, suponha que criamos uma Lista de Usuários em que, ao clicamos em um elemento da lista, sejamos redirecionados para uma tela de Usuário.

Suponha também que os primeiros usuários são João, Maria, José e Antônio, respectivamente. Logo, o Id de João (na ListView) é 0 (pois uma lista em Android começa a ser contada a partir de 0). O Id de Maria é 1, e assim por diante.

Porém, o Id de João, no Webservice, pode ser outro. E ao ser persistido no banco de dados, pode ser ainda outro. Por exemplo, suponha que o Id igual a 1, no Webservice, é o Id do usuário José. Assim, caso o desenvolvedor vá realizar uma operação de atualização de informação do usuário na tela de Usuário (como por exemplo, atualizar seu sobrenome), caso não seja realizada uma implementação com modelagem bem pensada, poderemos estar modificando o sobrenome de João (primeiro Id da ListView), quando deveríamos estar modificando o sobrenome de José (primeiro Id do Webservice).

Imagine agora que isto aconteça em um sistema como o do Banco do Brasil. Sim, isto ocorreu! No dia 09/12/2013, o sistema do Banco do Brasil, tanto para Android quanto iOS, foi tirado do ar, provavelmente por causa de um problema assim.


6. Muita atenção ao baixar um aplicativo, e à suas permissões


Ataques ao sistema Android são comuns, ainda mais porque o Android é o sistema operacional mobile mais utilizado do mundo.

Muitas vezes um usuário possui dados roubados, e sempre se porta no papel de acusador. Porém, raramente, ele presta atenção ao que lhe é pedido quanto este baixa um aplicativo do Google Play.

Quando baixamos um aplicativo do Google Play, antes de ele ser instalado em nosso dispositivo Android, é mostrada uma lista de permissões requeridas ao usuário. Infelizmente, a grande maioria dos usuários não presta atenção nesta tela. Isto acarreta em alguns problemas, como por exemplo os dados do usuários serem lidos.

Antes de mais nada, se formos baixar um aplicativo, temos que ler a respeito dele. Ler e se informar. Se ele possuir um bom conceito, então nada nos impede de o baixarmos. Assim, quando o download for realizado, e a permissão de “Ler dados do usuário” for apresentado, podemos concordar sem problemas. Porém, se formos baixar um aplicativo com reviews não tão bons, e ele requerer esta permissão, é preciso estarmos alertas.

Quase tudo em Android é criptografado e guardado na pasta Android/data do dispositivo Android. Porém, é melhor não arriscarmos.

Outro problema comum é um aplicativo requerer acesso ao GPS. Então, caso passemos despercebidos pelas permissões, não lendo o acesso à essa permissão, é possível que tal aplicativo habilite nosso GPS. Logo, 2 horas depois da instalação deste aplicativo, a bateria do celular estará em 10%. Isto ocorre porque o GPS foi habilitado “sem nossa permissão”.


7. 3G não funciona bem. Cacheie tudo!


Principalmente no Brasil, uma razão para tanto o Android quanto o iOS não serem tão difundidos no mercado de TI, é o 3G.

É um fato que a conexão 3G do Brasil vive falhando. Logo, se formos criar um aplicativo em Android, temos que estar cientes que toda e qualquer informação deve ser cacheada, assim que possível. Ou, claro, podemos criar uma aplicação que só funcione com Wifi. Mas aí, o que a diferirá de uma aplicação Web? Conceitualmente, nada.

Assim, se é a primeira vez que acessamos o aplicativo, o banco de dados não foi criado ainda. Logo, devemos obter os dados do aplicativo através de um Webservice. Então, tais dados serão persistidos no banco de dados.

Se não é a primeira vez que acessamos o aplicativo, o banco de dados já foi criado. Assim, basta lermos os dados deste, e os utilizarmos em nosso aplicativo.


8. Banco de dados sugando bateria


Ao desenvolvermos uma aplicação para Android, precisamos ter em mente que estamos desenvolvendo aplicações para dispositivos com processamento e memória mais modestos, quando comparados à um computador comum. Celular como o Samsung Galaxy Y possuem menos de 1GHz de frequência de CPU, um espaço de armazenamento de 180Mb, e uma memória RAM de apenas 290Mb. Logo, precisamos ter isso em mente na hora de criar uma aplicação.

Algo bem comum que acontece é não gerenciarmos corretamente o uso do banco de dados. Por exemplo, abrir a conexão com o banco, e esquecermos de fechá-la. Isto não acarreta grandes problemas para aplicativos com poucos acessos ao banco. Mas imagine um aplicativo com mais de 50 mil acessos!

A consequência disso é a vida útil da bateria ir diminuindo, além do aplicativo ser finalizado pelo sistema Android. Em Android, o máximo de memória que um aplicativo pode utilizar é 16Mb, tal qual afirmado em http://developer.android.com/training/displaying-bitmaps/index.html

Logo, se tivermos em nossa aplicação um acesso equivocado ao banco de dados, deixando seu acesso aberto e sem controle, e atingirmos mais de 16Mb de memória gerenciando o banco, automaticamente o sistema operacional Android “matará” nossa aplicação.

9. Ambientes de desenvolvimento “coringa”, como o Titanium


“Titanium” é um ambiente de desenvolvimento em que podemos criar aplicações nativas para Android e iOS utilizando códigos-fonte em linguagens JavaScript, que são compilados nativamente para Android ou iOS. Ele funciona muito bem para aplicações mais básicas, que não necessitam tanto do uso de componentes de hardware. Porém, quando precisamos utilizar certos componentes, o Titanium tende a nos trazer problemas.Isso não quer dizer que o Titanium seja uma ferramenta ruim. Ela é uma ferramenta adequada para certos tipos de aplicação. Ela possui seu nicho específico. Porém, se precisarmos criar aplicações mais robustas em Android, é aconselhável a criarmos nativamente, em Java, utilizando o SDK do Android.

Um exemplo de bug que assolou muitos desenvolvedor Titanium foi o bug relatado no link abaixo:http://stackoverflow.com/questions/15023762/titanium-mobile-android-memory-not-released-when-app-is-closed

Este bug já foi corrigido. Porém, como o Titanium é uma ferramenta criada pela empresa Appcelerator (e não pela OHA – Open Handset Alliance, que é o conjunto de empresas que controla o consórcio do Android), a chance de surgirem erros é bem maior.

Outros exemplos de ambientes de desenvolvimento “coringa” são o PhoneGap, da empresa Adobe, e o IBM Mobile Software, da empresa IBM.


10. Evitar crashes usando “try catch”


Ao desenvolvermos uma aplicação em Android, é comum realizarmos operações de acesso a diversos dispositivos, como banco de dados, acesso à rede, etc.

Por exemplo, ao obtermos dados de um Webservice em formato JSON, acessamos a rede e fazemos o parseamento dos dados de JSON para uma classe “model” do nosso aplicativo. Porém, durantes estas operações, há possibilidades de ocorrerem erros. Por este motivo, é vital sempre realizarmos tais operações utilizando o comando “try catch”. Assim, caso ocorra algum erro, ao invés de ele “explodir” para o usuário, ele será tratado pelo comando “catch”.


11. Usar switch com constantes de tipos Integer


Um motivo comum de estresse para alguns desenvolvedores é não conseguir colocar constantes do tipo Integer em um comando “switch”. Por exemplo, definirem variáveis como “public static final Integer ID_JOAO = 1”, “public static final Integer ID_JOSE = 2” e “public static final Integer ID_MARIA = 3” e as colocarem na cláusula “case” do comando switch. Porém, isto acarreta em um erro de compilação que não faz sentido para muitos desenvolvedores.

A solução para este problema é, simplesmente, trocar o tipo das constantes de Integer para int. Integer é a classe-objeto do tipo primitivo int. Com ela, podemos verificar se a variável é nula, e também fazer cast de Object para Integer. Porém, não podemos usá-la em um switch.


12. Construtores de Adapters não permitem personalização


Podemos criar uma lista de nomes de usuário utilizando tanto construtores de Adapters (como o ArrayAdapter e o SimpleAdapter), quanto classes que extendam de ArrayAdapter ou BaseAdapter. A 2ª opção é mais trabalhosa, mas a mais indicada. Por que? Porque ela permite a personalização de mais componentes nos Adapters que as TextView.

Por padrão, os construtores “public ArrayAdapter (Context context, int resource, int textViewResourceId, T[] objects)” e “public SimpleAdapter (Context context, List<? extends Map<String, ?>> data, int resource, String[] from, int[] to)”, que podem ser usados diretamente na Activity que infla o Adapter, populam dados apenas nas TextView’s do layout de cada elemento da lista.

Porém, e se precisarmos popular também imagens, botões, etc? Neste caso, os construtores não nos ajudam, uma vez que populam dados apenas em TextView’s. Sendo assim, obrigatoriamente, precisamos popular dados em Adapters que extendam de Arraydapter ou BaseAdapter.

Muitos desenvolvedores, por não entenderem esta lógica, ficam sem saber planejar a melhor maneira de se popular uma lista. O fato é que há ocasiões em que é preferível utilizarmos construtores (que populam TextView’s de forma bem otimizada), e ocasiões em que é preferível utilizarmos classes que extendam de BaseAdapter ou ArrayAdapter, pois nestas conseguimos mapear qualquer tipo de componente visual que utilizarmos no layout dos elementos da ListView.


13. A classe Thread bloqueia a UI do usuário


Se precisarmos utilizar processamento paralelo em Android, podemos utilizar as classes AsyncTask e Handler. E também a classe Thread. Porém, utilizando esta classe, “travaremos” a interface de usuário (UI). O efeito visual de tal travamento é extremamente deselegante, pois até mesmo telas de “Loading,..” ficarão travadas.

Por este motivo, é extremamente não-aconselhável utilizarmos a classe Thread em Android.


14. No developer.android.com tem tudo que um desenvolvedor precisa saber



No site citado acima há tudo que precisamos saber sobre Android. A falta de leitura deste conteúdo costuma acarretar erros bem bobos.

Por ser professor percebo isto nitidamente. Muitos e muitos erros que acontecem em Androis seriam evitados caso o desenvolvedor lê-se o que a Google expõe sobre cada classe ou método do Android.

15. Confundir um arquivo da pasta res/layout/main.xml com um arquivo da pasta res/menu/main.xml


Muitos desenvolvedores costumam confundir os conteúdos destas duas pastas. A primeira é a pasta referente à interface gráfica das telas em Android. A segunda é a pasta referente aos menus em Android.Assim, por exemplo, não podemos inflar no método setContentView(), de uma classe que extende de Activity, um componente do tipo R.menu.main. Em contrapartida, não podemos inflar no método MenuInflater.inflate() um componente do tipo R.layout.main.

Este é um erro bem frequente que ocorre, cuja mensagem de erro não é muito reveladora. Ela costuma mais atrapalhar do que ajudar.


16. Confundir View.onClickListener com DialogInterface.OnClickListener


Um dos erros mais clássicos que existem no universo Android.

Ao escrevermos “implements OnClickListener” no Eclipse, e digitarmos CTRL + SHIFT + O (para importar as classes de Android da nossa Activity), o Eclipse nos dará duas opções: “android.content.DialogInterface.OnClickListener” ou “android.view.View.OnClickListener”. Qual escolheremos? Depende.

Se quisermos obter o evento de um componente que extende de View, como Button, EditText, TextView, ListView, etc, devemos escolher “android.view.View.OnClickListener”. Se quisermos obter o evento de um Dialog, devemos escolher “android.content.DialogInterface.OnClickListener”.

Este erro é bem comum pois o desenvolvedor geralmente costuma programar de maneira bem veloz, o que tende a gerar desatenção. O importante é ele saber identificar tal erro.


17. BufferOverflowException


Com a atualização do SDK do Android para a API 19, versão 4.4, vulgarmente conhecido como KitKat, um erro começou a aparecer para os desenvolvedores. Este erro é o BufferOverflowException. Ele ocorre quando o SDK de um projeto não é fixado na API 19.

Antes da atualização do SDK para a API 19, este erro não acontecia. Agora, caso ele ocorre, basta mudarmos a API do nosso projeto de Android para a API 19.


18. Eclipse deu pau? Dê “Clean” no projeto


O Eclipse (e sua variação ADT) é o ambiente de desenvolvimento mais utilizado para se desenvolver aplicações para Android. Porém, ele gerencia muitos componentes, bibliotecas, classes e funcionalidades ao mesmo tempo. Desta forma, é comum que, após alguns minutos ou horas de uso, ele perca algumas referências em sua memória.

Quando isto acontece, um projeto que está funcional e sem erros começa a dar alguns problemas de referências de classes e bibliotecas. Isto é fácil de identificar: é como se o Eclipse “pirasse”, pois do nada começa a dar erro em projetos que estávamos testando sem problemas.

Quando isto acontecer, tudo que precisamos fazer é clicar no menu “Project”, e depois em “Clean...”, e escolher o projeto que estamos mexendo. Ao fazer isso, todos os binários do projeto (armazenados na pasta bin/) serão deletados, o que fará com que os problemas “sem sentido” sumam.


19. Mapas V1 não funciona mais, e Mapas V2 mudou em novembro de 2013


Até dezembro de 2012, os mapas em Android utilizavam a mesma API de mapas criada em 2007. Porém, neste mês, a Google anunciou que a API de mapas mudaria. Desta forma, qualquer projeto que rodasse mapas deveria ser adaptado para a nova API (senão, deixaria de funcionar). Logo, muitos erros em projetos que possuem mapas, e que são anteriores à dezembro de 2012, se devem à isso.

Com a nova API, muita coisa mudou, como as permissões necessárias para se rodar mapas, e também funcionalidades bem interessantes, como a modelagem 3D do zoom dos mapas, e o Tilt e Bearing (rotação dos mapas em um total de 360º).

Porém, em novembro de 2013, novamente, a API de mapas mudou. Não foi uma mudança significativa, foi uma mudança sutil. Porém, mesmo que sutil, ela impossibilitou que aplicativos com Mapas na V2 (versão 2) funcionassem. O que mudou foi a necessidade de adição de uma nova “meta-data” no arquivo AndroidManifest.xml, e também o layout do site do Google Cloud Console (e consequentemente, todo o fluxo de cadastro de um aplicativo que irá utilizar o Google Maps V2 para Android).

Além da mudança deste fluxo de cadastro, mudou a criação da API Key do projeto em que utilizaremos o Google Maps V2.


20. Copiou um projeto inteiro e gerou um novo? Pode se preparar para problemas com a classe R


Para economizar tempo, muitos desenvolvedores que precisam criar um projeto novo (mas que possuem semelhanças à um projeto anterior) costumam copiar e colar este projeto para o workspace corrente, e apenas mudar o nome deste projeto.

Isto acarreta muitas referências ao projeto antigo, através de: “import R.<pacote_do_projeto_anterior>”. A única maneira de sumir com estas referências (que acarretam dezenas de erros) é modificar o pacote da aplicação atual (em cada tela do aplicativo atual), atualizar esta informação no arquivo AndroidManifest.xml, e dar CTRL + SHIFT + O em cada tela da aplicação. Desta forma, teremos sumido com as referências do projeto antigo. Todo este processo toma muito tempo, e muito trabalho braçal.

Se tivermos que criar um projeto novo, similar a um anterior, o aconselhável é criar um projeto novo, do zero, e copiar o conteúdo das classes Java e telas XML, um a um, utilizando CTRL + A (no Eclipse, copiar todo o conteúdo de um arquivo), e colando-os em arquivos novos do projeto novo. Desta forma, a referência que será feita entre estes arquivos será relativa ao projeto atual, e não a qualquer outro projeto.


21. ActivityNotFoundException


Outro erro clássico, muito comum em Android.

Toda Activity que formos acessar em Android precisa ser declarada no arquivo AndroidManifest.xml, sem exceção. Caso chamemos, por exemplo, uma tela chamada CadastroActivity.java, mas ela não esteja declarada no AndroidManifest.xml, nosso aplicativo parará, e aparece no Logcat uma mensagem de erro do tipo ActivityNotFoundException.


22. Usar Logcat e o depurador é altamente aconselhável


Quanto maior o aplicativo, maior sua complexidade, e mais erros ele pode nos trazer. Por este motivo, é altamente aconselhável utilizarmos várias tags de Log em nosso aplicativo. Assim, caso aconteça algum erro, saberemos em que ponto nosso aplicativo parou.

Infelizmente, nem todos os erros que ocorrem em Android nos trazem uma mensagem de erro descritiva. Há casos de erro em que sequer uma mensagem aparece. Nestes casos, se não tivermos estrutura de Log bem definida, estaremos em apuros.

Outra ferramenta que nos auxilia imensamente é o depurador. Caso nosso aplicativo pare em uma linha específica de um arquivo Java, basta setarmos um Breakpoint, e analisarmos o conteúdo das variáveis de tal linha. É comum encontrarmos erros como uma variável estar nula, ou com um Id errado, ou passando um valor incompleto para outro método, etc.

Um desenvolvedor Android que utilize estas duas ferramentas dificilmente dependerá de outra pessoa. Um desenvolvedor que sabe utilizar estas ferramentas dificilmente perderá tempo consultando seus erros à um outro desenvolvedor.


23. Java 1.7? Use o 1.6


Quando uma classe simples de Java (como a classe List) começa a dar erro, isso é sinal de que o JDK do Java está mal configurado. Geralmente, quando se atualiza o JDK da versão 1.6 para a 1.7, isto é replicado para o Eclipse. Mas para rodarmos aplicações do Android sem complicações, o JDK que utilizaremos no Eclipse precisa estar na versão 1.6. Se ela estiver na versão 1.5, pior ainda.

Caso esses erros de classes básicas comecem a ocorrer, clique com o botão direito no projeto corrente, e então em “Properties”, e em “Java Compiler”, e mude a versão do Java para a 1.6, e também marque o checkbox “Use default compliance settings”, para replicar demais opções à versão 1.6.


24. Erro de “jar mismatch”


Alguns componentes em Android, nativamente, só funcionam a partir da API  11 (como a ActionBar). 

Outras, a partir da API 14 (como Fragmentos). Estes dois componentes não funcionam nativamente em API’s inferiores à 11. Logo, dispositivos com Gingerbread e Froyo não rodam tais componentes.

Porém, há uma maneira de se conseguir isto. Isto é possibilitado através do arquivo “android-support-v4.jar”, que implementa tais componentes em API’s inferiores.

Uma biblioteca famosa por possibilitar esse suporte é a ActionBarSherlock. Com essa biblioteca, através do uso da “android-support-v4.jar”, conseguimos utilizar ActionBar e Fragmentos em dispositivos Gingerbread e Froyo. Porém, em casos específicos, podemos não conseguir rodá-la, obtendo um erro de “jar mismatch”.

Por exemplo, se tivermos um projeto chamado “TesteSupport”, que utiliza a ActionBarSherlock, precisamos referenciar neste projeto que utilizaremos a biblioteca ActionBarSherlock. Isto implica que temos um projeto referenciando outro.

Como ambos utilizam a biblioteca de suporte, devem possuem em suas pastas libs/ o arquivo “android-support-v4.jar”. Porém, em ambos, a versão da biblioteca de suporte tem de ser idêntica!Caso não seja, ocorrerá um erro de “jar mismatch” quando formos rodar o programa.


25. Evite fechar o emulador


Abrir um emulador do Android demanda bastante memória e processamento. É um processo bem pesado. Versões acima da API 14 (versão 4.0) costumam demandar mais de 4Gb de memória RAM.

Por este motivo, é altamente aconselhável que, uma vez que o emulador for aberto, ele só seja fechado quando o computador foi finalizado. Com certeza isto faz diferença quando um desenvolvedor tem um aplicativo à ser desenvolvido, e principalmente, um tempo escasso para tal tarefa.


26. Ao enviar uma nova versão ao Google Play, mudar a tag “android:versionCode” do arquivo AndroidManifest.xml


Um erro comum que desenvolvedores cometem devido à falta de atenção é este.

No todo do arquivo AndroidManifest.xml existem os atributos “android:versionCode” e “android:versionName”. O primeiro é um número inteiro que DEVE SER MUDADO quando enviarmos um novo arquivo .apk ao Google Play. Ou seja, uma versão atualizada do nosso aplicativo. O segundo é o que será mostrado ao usuário quando a nova versão do aplicativo chegar ao Google Play.

Ou seja, podemos estar enviando ao Google Play a versão 4 do nosso aplicativo (versionCode), mas atualizando o “android:versionName” para “1.0.4”. É isto que o usuário enxergará.

Caso já tenhamos enviado ao Google Play uma versionCode “4”, e enviarmos nosso .apk com a mesma versionCode, a uma mensagem de erro será mostrada pela Google.


27. Use CTRL + SHIFT + O sempre


O atalho mais usado no Eclipse é este. Com ele, atualizamos e importamos as bibliotecas que estamos utilizando em nosso código-fonte Java.

Por exemplo, se declaramos um Button, e em seguida setamos “button.setOnClickListener”, digitando CTRL + SHIFT + O, automaticamente o pacote “import android.view.View.OnClickListener” será importada ao nosso arquivo Java.

A razão de utilizarmos o ambiente de desenvolvimento Eclipse é facilitar, agilizar a criação de aplicativos para Android. Tendo esta premissa, o CTRL + SHIFT + O se torna uma ferramenta bem útil aos desenvolvedor Android, uma vez que não precisamos saber de cor quais pacotes cada classe do Android possui.


28. Não esquecer “Add unimplemented methods”


Quando declaramos um botão, e desejamos clicá-lo, devemos setar OnClickListener neste botão. Isto é feito no escopo da classe em que o botão está, da seguinte forma: “public class MyActivity extends Activity implements OnClickListener”.

Uma vez que isto é feito, aparece no Eclipse um erro na linha acima, no início dela, em forma de um “x” vermelho. Clicando-se nele (ou digitando CTRL + 1 na mesma linha), aparecerão sugestões de ações. A primeira delas é a “Add uninplemented methods”. Isto significa que poderemos implementar os métodos que este listener especifica. Neste caso, o método “onClick”.

Como este método é um método de um listener (que nada mais é do que uma interface ou classe abstrata), em cima do método “onClick” teremos uma annotation “Override”. Ela nada mais é do que uma anotação que nos diz que o método “onClick” é a implementação de um método abstrato do listener “onClickListener”. Assim, se clicarmos em qualquer componente gráfico da nossa Activity, seremos redirecionados imediatamente para o método “onClick” (com Override).

Um erro bem comum que alguns desenvolvedores cometem é implementar o método “onClick” sozinhos, sem intermédio da expressão “implements OnClickListener”, e então executar o aplicativo. Neste caso, o que acontecerá é que nunca o fluxo do aplicativo chegará até o método “onClick”, pois o método “onClick” precisa obrigatoriamente estar relacionado com a expressão “implements OnClickListener”. Isto só é realizado quando tivermos a annotation “Override” em cima do método “onClick”.

Outro erro comum é o desenvolvedor esquecer de setar o listener do botão, através da expressão “button.setOnClickListener(Context context)”, onde “context” é o contexto da nossa Activity.


29. Versões inferiores à 2.2 não são aconselháveis


Acessando o site de estatísticas da Google, podemos perceber que a versão 2.2 possui menos de 1,5% dos usuários de Android de todo o mundo: http://developer.android.com/about/dashboards/index.htmlA versão 2.1 nem aparece entre as estatísticas!

Por este motivo, é aconselhável incluir apenas versões a partir da 2.3 no aplicativo Android que formos desenvolver. Hoje (12/12/2013) a versão 2.3 possui 24,1% dos usuários. E as versões 4.0 para cima possui mais de 72% dos usuários.

Muitos componentes em Android não funcionam em versões como a 2.2. A maioria dos componentes utilizados em desenvolvimento Android, hoje em dia, funcionam a partir da versão 2.3. Desta forma, incluir a versão 2.2 no desenvolvimento de um aplicativo implica em aumentar a complexidade do desenvolvimento (além da ocorrência de erros no aplicativo).

30. Sua tela travou e sequer um erro apareceu no LogCat? Um ANR ocorreu!


A pior coisa que pode acontecer com um aplicativo Android é o ANR (Application Not Responding). Quando este erro acontece, sequer um "Caused by" aparece no Logcat.

Em Android, quando uma aplicação fica sem resposta por um período de tempo, é mostrada uma caixa de diálogo que diz o seu aplicativo parou de responder, como é mostrado abaixo.


Quando isto acontece, seu aplicativo ficou sem responder por um período considerável de tempo. Então, o sistema oferece ao usuário uma opção para sair do aplicativo. É fundamental projetar seu aplicativo para que o sistema NUNCA exiba um diálogo de ANR para o usuário.

O sistema Android mostra um ANR se a aplicação não consegue responder à uma entrada de dados do usuário. Por exemplo, se uma aplicação bloquear alguma operação de entrada/saída (usualmente um acesso de rede) na thread de UI (User Interface), então o sistema não consegue processar eventos de entrada do usuário. Ou, talvez, o aplicativo gaste muito tempo construindo uma estrutura de memória ou computando a próxima jogada em um jogo na thread de UI. Sempre é importante estar certo sobre a eficiência destas operações.


Em qualquer situação que seu aplicativo realize operações potencialmente vastas, não se deve realizar tais opearções na thread de UI. Neste caso, o mais aconselhável é criar uma thread específica para fazer todo o trabalho. Isto evita a thread de UI (que deixa a interface do usuário em loop) rodando e previne o sistema de concluir que seu código está travado. 

Em Android, respostas de processamento das aplicações são monitoradas pelo Activity Manager e Window Manager. O Android irá disparar o ANR para uma aplicação específica quando ele detectar uma das seguintes condições:

  • Ausência de resposta à um evento de entrada (como uma tecla pressionada ou um evento de clique na tela) que demora mais de 5 segundos.
  • Um BroadcastReceiver não tiver terminado de executar em 10 segundos.

Geralmente, uma demora de 100 à 200 mili segundos já causará ao usuário um travamento visível na aplicação. Algumas dicas para evitar o ANR são:

  • Se sua aplicação está processando em background em resposta à uma entrada de dado do usuário, mostre o progresso que está sendo realizado (por exemplo, usando uma ProgressBar).
  • Se sua aplicação possui um pré-processamento à primeira tela à ser mostrada, mostre uma SplashScreen ou renderize a tela principal o mais rápido possível, indicando um carregamento em processo, e preencha tais informações de forma assíncrona. Deve-se mostrar o progresso que está sendo feito, a fim de mostrar ao usuário que a aplicação não congelou.
  • Utilize ferramentas como Systrace e Traceview para determinar gargalos de processamento de sua aplicação.

-----

Caso alguém tenha uma dúvida, crítica ou sugestão, sinta-se à vontade.

quarta-feira, 4 de dezembro de 2013

Alarmes e Broadcast Receivers

Olá à todos.

Em Android, Alarmes e Broadcast Receivers caminham junto. Por isso, veremos ambos os tópicos de forma conjunta.

A classe AlarmManager fornece acesso aos serviços de alarme do sistema. Estes permitem que você programe seu aplicativo para ser executado em algum momento no futuro. Quando o alarme dispara, a Intent que havia sido registrada por este é transmitido pelo sistema, iniciando automaticamente o aplicativo de destino se ele não estiver em execução. Alarmes registrados são mantidos no sistema enquanto o dispositivo está dormindo (e pode, opcionalmente, acordar o dispositivo durante esse tempo), mas serão apagados se ele for desligado e reiniciado.

O AlarmManager mantém o alarme na CPU enquanto o método onReceive() (de um Broadcast Receiver) estiver em execução. Isso garante que o telefone não durma até que seja finalizada a transmissão do alarme. Uma vez que o onReceive() retorne, o AlarmManager liberará o alarme da CPU. Isso significa que seu telefone irá dormir em alguns casos (assim que o seu método onReceive() completar).

Já um Broadcast Receiver simplesmente responde à mensagens transmitidas por outros aplicativos ou do próprio sistema. Estas mensagens são por vezes chamadas eventos ou Intent’s. Um Broadcast Receiver é um componente em Android que permite interagirmos com os eventos do sistema ou da aplicação. Todos os Receivers inscritos para um evento serão notificados pelo Android uma vez este evento aconteça.

Exemplo de Alarme com Broadcast Receiver


Em nosso primeiro exemplo, criaremos um exemplo simples de alarme em que uma Activity interage com um Broadcast Receiver.

Nele, primeiramente, chamaremos um alarme 5 segundos depois da Activity ser chamada. E, por fim, o faremos repetir a cada 10 segundos.

Antes de mais nada, precisamos definir nosso Broadcast Receiver no AndroidManifest.xml.


Podemos notar acima que o Broadcast Receiver foi definido através de um IntentFilter. Esta é outra maneira de se declarar um componente (Activity, Service, Broadcast Receiver) no AndroidManifest.xml. Como isto é chamado, veremos ao longo deste exemplo.

A Activity que chama nosso alarme é mostrada abaixo.


No método onCreate() é chamado o método schedule(). Neste método, inicialmente, colocamos o IntentFilter “EXECUTE_ALARM” dentro de uma PendingIntent. Depois, inicializamos a hora em que o alarme será chamado, através do Calendar. E então, chamamos o alarme, utilizando tanto a PendingIntent como o Calendar. No caso do nosso exemplo, o alarme será chamado 5 segundos depois que a Activity for inicializada.

Após estes 5 segundos, a PendingIntent será chamada. Ou seja, o fluxo do nosso aplicativo irá para a classe AlarmReceiver (que é o Broadcast Receiver associado com o IntentFilter “EXECUTE_ALARM”).

A classe AlarmReceiver é bem simples.


Ou seja, nesta classe tudo que fazemos é chamar um Toast.

E pronto! Acabamos de criar nosso primeiro exemplo de alarme em Android.

Agora, iremos fazer este alarme ser repetido a cada 10 segundos. Para isto, criaremos uma classe chamada RepeatedAlarmActivity.


A principal diferença entre a RepeatedAlarmActivity e a SimpleAlarmActivity é o comando abaixo:
alarm.setRepeating(AlarmManager.RTC_WAKEUP, time, REPEATING_TIME, p), que realiza a repetição do alarme, à cada 10 segundos.

De resto, a RepeatedAlarmActivity é praticamente idêntica à SimpleAlarmActivity.

Agora, veremos um exemplo mais complexo de alarme, em que utilizaremos, além das classes AlarmManager e BroadcastReceiver, as classes Notification e Service.

Exemplo de alarme com Service e Broadcast Receiver usando RECEIVE_BOOT_COMPLETED


Neste exemplo, programaremos o alarme para ser executado em uma hora específica. E para isto, precisamos assegurar que o alarme será executado mesmo que o celular seja reiniciado.

Para isto, precisamos modificar nosso Broadcast Receiver para executar quando o celular for ligado. Isto pode ser feito através do IntentFilter BOOT_COMPLETED. Além deste IntentFilter, precisamos declarar uma permissão específica. Ambos são mostrados abaixo.


Como podemos notar pelo AndroidManifest.xml, temos neste exemplo duas Activity, um Service e um Broadcast Receiver, e este funciona tanto quando nosso aplicativo estiver sendo executado, quando o celular for reiniciado.

Porém, se ele for executado quando o BOOT do sistema for realizado, não poderemos utilizar logs  de Logcat, e tampouco debugar a aplicação, uma vez que ao ser reinicializado, o sistema fecha todas as aplicações que estavam abertas antes de seu desligamento.

Por este motivo, neste exemplo, utilizaremos o conceito ensinado no Capítulo 11 para criar “logs” em arquivos de texto dentro de nosso dispositivo Android.

Logo, neste aplicativo, utilizaremos a classe FileUtils (tal qual demonstrado no Capítulo 11) para salvar dados de log em um arquivo localizado em nosso dispositivo Android. Esta classe será chamada diretamente pela classe LogUtils, que é mostrada abaixo.


Ou seja, nesta classe, tudo que faremos será imprimir uma mensagem em um arquivo.

Agora que temos assegurado nosso log, podemos executar a lógica que chamará o alarme.

Como pudemos ver no AndroidManifest.xml, neste exemplo temos duas Activity, um Service e um Broadcast Receiver. A lógica que usaremos será análoga ao primeiro exemplo de alarme, ensinado na seção anterior deste Blog. Ou seja, na tela principal chamaremos um Broadcast Receiver.

No exemplo anterior, o chamávamos através de um IntentFilter chamado EXECUTE_ALARM, colocado em uma Intent que era anexada à um PendingIntent. Neste exemplo, o chamaremos através do próprio nome do Broadcast Receiver, que é PushNotificationReceiver.

Abaixo podemos ver como este Receiver é chamado na tela principal.


Após ser chamado, este Receiver executará primeiramente o método onReceive(). Como podemos ver abaixo, ele recebe um contexto (pois Receivers nativamente não possuem contexto), e chamar o método schedule().


É no método schedule() que chamamos o Service, colocando-o em uma chamada de Intent, e colocando esta em uma PendingIntent. Neste método, caso a PendingIntent não tenha sido criada (ou seja, o alarme não tenha sido programado ainda), chamamos o método setAlarm(). Caso ela tenha sido, imprimimos em nosso log uma mensagem de alerta.


Um dos argumentos do método setAlarm() é um retorno do método getAlarmTime(). Neste método, obtemos o horário ao qual programamos nosso alarme (que é definido no arquivo string.xml). E nele também realizamos a comparação mais importante de todo o Receiver: a atualização da variável currTime.

O if deste Receiver atualiza o valor desta variável usando a seguinte lógica: se o alarme não tocou no dia atual, não entramos neste if. Porém, se o alarme já tocou (ou seja, se a hora e minuto correntes forem maiores que a hora e o minuto do alarme), atualizamos o currTime para o dia de amanhã, pelo comando currTime += 24 * 60 * 60 * 1000. Estamos somando o tempo atual à 1000 milisegundos, multiplicados por 60 segundos, multiplicados por 60 minutos, multiplicados por 24 horas (ou seja, estamos somando currTime ao dia de amanhã, 24 horas posteriores ao momento atual).


Em seguida, setamos currTime na variável calendar, e retornamos este valor em milissegundos.

Em sequência, temos a definição do método setAlarm().


Neste método, chamamos o AlarmManager através dos já inicializados pendingIntent e time (que nada mais é do que o retorno do recém explicado getAlarmTime()).

E assim chegamos ao fim do nosso PushNotificationReceiver, que chama o PushNotificationService. Este Service será chamado apenas quando chegarmos EXATAMENTE ao horário programado pelo AlarmManager. E como definimos um Receiver em BOOT, mesmo que o celular seja reiniciado, o Service será chamado.

Então,vamos à implementação do Service:


Podemos notar acima que temos três constantes:

  • NOTIFICATION_INTENT É uma String que define um IntentFilter (tal qual esta definido em nosso AndroidManifest.xml). Neste caso, define a tela NotificationActivity.java.
  • FROM_NOTIFICATION_EXTRA_KEY Identificador de um Extra de Intent que enviaremos à tela NotificationActivity, com o intuito de cancelar a notificação chamada pelo Service.
  • NOTIFICATION_ID Identificador da nossa notificação. É o dado que será enviado juntamente com a FROM_NOTIFICATION_EXTRA_KEY para a tela NotificationActivity.


Temos também um atributo que armanena uma instância de FileUtils, ao qual chamaremos a classe LogUtils.

Repare também que temos o evento onBind(). Este evento não tem muita importância na implementação do nosso Service.


Acima podemos ver o método onCreate(), onde chamamos o método createNotification() (onde criamos a notificação); cancelamos as Intent anteriores; retransmitimos o alarme para o dia de amanhã; e, por fim, finaliza a si mesmo.

Logo abaixo, temos o método createNotification(), que nada mais é do que uma revisão do que foi explicado no Blog, neste tópico. Nele, criamos uma notificação que chama a Intent NotificationActivity.


E abaixo, temos o método que cancela alarmes anteriores. Este método é chamado entre o método createNotification() e o comando sendBroadcast(new Intent(this, PushNotificationReceiver.class)). Ou seja, neste exato momento (entre o método o comando descritos acima), precisamos certificar que nenhum alarme está programado para ser tocado (uma vez que precisamos programar um novo alarme). Após nos certificarmos disso, aí sim podemos chamar o próximo alarme pelo método sendBroadcast().


Repare acima que a PendingIntent chamou o IntentFilter NOTIFICATION_INTENT. Verificando nosso AndroidManifest.xml, podemos notar que a classe associada à este IntentFilter é a PushNotificationService. Ou seja, estamos obtendo as PendingIntent associadas ao nosso Service.

Por fim, vamos à implementação da tela NotificationActivity, que é a tela chamada após o alarme ser tocado.


Acima podemos notar que chamamos o método cancelNotificationIfNeeded(). Basicamente é isto que faremos nesta tela. Uma vez que a classe PushNotificationService é executada, e a notificação é mostrada, chamamos a tela atual via PendingIntent, e nela precisamos cancelar a notificação. Caso contrário, ela será mostrada eternamente.


Acima podemos notar de que maneira a notificação é cancelada: recebemos um Extra de Intent do nosso Service (que nada mais é do que o id da nossa notificação), e com ele, a cancelamos.

E isto é tudo! Já temos conhecimento para criar um alarme executável em um horário específico, mesmo que para tal precisemos reiniciar nosso celular.

-----

Caso alguém tenha uma dúvida, crítica ou sugestão, sinta-se à vontade.

segunda-feira, 2 de dezembro de 2013

Arquivos

Olá à todos.

Neste tópico mostraremos como salvar dados em um arquivo, dentro do espaço de armazenamento de um dispositivo celular.

O sistema Android usa um sistema de arquivos que é semelhante aos sistemas de arquivos baseados em disco, em outras plataformas. Um objeto File é adequado para leitura ou escrita de grandes quantidades de dados em ordem de início-fim, sem paralizações.

Todos os dispositivos Android têm duas áreas de armazenamento de arquivos: armazenamento "interno" e "externo". Estes nomes vêm desde o início da criação do Android, quando a maioria dos dispositivos oferecidos possuíam memória não-volátil (memória interna), além de um meio de armazenamento removível, como um cartão micro SD (armazenamento externo).

Alguns dispositivos dividem o espaço de armazenamento permanente em partições "internas" e "externas". Então, mesmo sem um meio de armazenamento removível, há sempre dois espaços de armazenamento, e o comportamento da API é o mesmo se o armazenamento externo é removível ou não. As listas a seguir resumem os fatos sobre cada espaço de armazenamento.


Armazenamento interno:
  • Sempre está disponível.
  • Arquivos salvos aqui são acessíveis apenas pelo seu aplicativo, por padrão.
  • Quando o usuário desinstala um aplicativo, o sistema remove todos os arquivos do seu aplicativo do armazenamento interno.


Armazenamento externo:
  • Não está sempre disponível, porque o usuário pode montar o armazenamentos externo como um armazenamento USB, e em alguns casos removê-lo do dispositivo Android.
  • É amplamente legível, então arquivos salvos aqui podem ser lidos sem nossa permissão.
  • Quando o usuário desinstala o aplicativo, o sistema remove os arquivos do aplicativo apenas se os salvarmos no diretório retornado por getExternalFilesDir().

Permissões para salvar e ler arquivos em um dispositivo Android


Para salvarmos arquivos em um dispositivo Android, devemos declarar a permissão WRITE_EXTERNAL_STORAGE no arquivo AndroidManifest.xml.
Analogamente, para lermos dados de arquivos de um dispositivo Android, devemos declarar a permissão READ_EXTERNAL_STORAGE, tal qual é demonstrado abaixo.


Manipulando dados em um arquivo interno à um dispositivo Android


Para manipular dados em um arquivo, seja criando, editando, ou deletando seu conteúdo, precisamos criar diversos métodos de acesso a arquivos. Isto será demonstrado através de um robusto exemplo.

Este exemplo possuirá uma tela principal, e uma classe com todos os métodos que interagem com arquivos. Abaixo temos a tela principal.


Podemos ver que ela define uma instância da classe FileUtils (que é a classe que fará a interação com os arquivos), e que o método fileLog() é um método que salva uma String em um arquivo.

A classe que interage com os arquivos é mostrada abaixo.


Acima podemos ver a pasta em que o arquivo será salvo. Ela é obtida através do método Utils.getPackageName(Class<?> clazz). Este método retorna o caminho do pacote da tela passada como parâmetro.

Para entende-la, vamos dar uma olhada na classe Utils.java.


Neste caso, o retorno do método acima será “br.example.filemanager”. Logo, o comando abaixo retornará “/Android/data/br.example.filemanager/”: public static final String CACHE_DIR = "/Android/data/" + Utils.getPackageName(MainActivity.class) + "/";

Seguindo nossa classe FileUtils, temos abaixo o método saveFile(), que possui uma lógica bem interessante. Nele, inicialmente verificamos se o dispositivo de armazenamento está montado. Se ele estiver, criamos o diretório descrito acima, e após isto, obtemos o caminho do arquivo à ser manipulado.

Se este arquivo já existir, chamamos o método editFile(). Se ele não existir, chamamos o método createFile().


Em seguida, temos o método que verifica se dispositivo de armazenamento está montado.


E logo abaixo, um método que verifica se um arquivo existe.


E abaixo, métodos que criam um diretório e um arquivo.


Já o método readFile() será usado pelo método editFile() para atualizar o conteúdo de um arquivo.


E, finalmente, o método que chamamos diretamente através da MainActivity.


E isto é tudo. Com este exemplo bem simples, que apenas escreve o texto “Printing into a file...” em um arquivo de uma pasta dentro de um dispositivo Android, conseguimos ilustrar toda a lógica envolta na criação e atualização de conteúdo de um arquivo em Android.


-----

Caso alguém tenha uma dúvida, crítica ou sugestão, sinta-se à vontade.