Open Handset Alliance:
Consórcio formado por mais de 50 empresas
Basicamente, android tem as seguintes camadas:
O foco deste minicurso está na primeira camada citada: A camada de Aplicações.
O que torna as aplicações android extremamente poderosas são as APIs providas pela plataforma, que permitem às aplicações acessarem vários recursos. Algumas delas:
| UI Widgets
Botões, listas, caixas de textos, etc. |
android.widget.* android.view.* ... |
| Telephony Ligações telefônicas e envio/recebimento de SMS. |
android.telephony.IPhone android.telephony.gsm.SmsManager |
| Camera | android.hardware.CameraDevice |
| Web client | android.webkit.WebView |
| HTTP | org.apache.http.client.* |
| Database (SQLite) | android.database.* |
| Google Maps | com.google.android.maps.MapView |
| Location | android.location.* |
| Multimedia | android.media.* |
Antes de tudo, vamos precisar do Android SDK.
Para fazer o download, vá até developer.android.com/sdk e selecione a versão correta para seu sistema operacional.
Após completado o download, execute o instalador. Para que seja possível a instalação, você precisará ter o Java SDK previamente instalado.
Clique em Next
Nesta tela, o instalador avisa que encontrou o Java SE Development Kit (JDK)
Para que não ocorra nenhum erro aqui, é importante garantir que o JDK está corretamente instalado e as seguintes variáveis estão corretamente setadas:
Nesta tela, escolha o diretório de instalação e clique em next.
Atenção: É necessário escolher um caminho sem espaços, senão ocorrerá um erro "invalid command-line parameter" ao tentar executar o emulador.
Clique em Next novamente. (Esta tela apenas configura o nome do atalho)
Com a instalação completa, clique em Next.
Clique em "Finish" para terminar a instalação e iniciar o SDK Manager
No SDK Manager, clique em "Available packages" no menu à esquerda.
Depois disso, expanda o item "Android Repository" e selecione a versão 2.3.3 do SDK. Essa é a última versão do SDK para telefones celulares (As versões 3.* são destinadas a tablets)
Clique em "Install Selected", e na próxima tela aceite as licenças dos pacotes (Selecione "Accept All")
Agora é só esperar o download do SDK terminar.
O SDK Android possui as bibliotecas padrão para desenvolver os aplicativos android e algumas ferramentas de desenvolvimento. Um exemplo é o emulador, importante para testarmos nossas aplicações sem a necessidade de um hardware.
Para usarmos o emulador android, é preciso antes entender o conceito de AVD - Android Virtual Device
O AVD serve para "representar" um dispositivo real, ou seja, ele vai ter as suas características, como tamanho de tela, quantidade de memória, etc.
Dessa forma, o primeiro passo para a execução do emulador é a criação de um AVD, sobre o qual o emulador funcionará.
Para isso, vamos novamente usar o SDK Manager, mas agora selecionando o menu "Virtual Devices":
Na tela para a criação do AVD, é necessário escolher um nome, que representará o AVD, e o "target", que é versão de android que rodará nele. Como instalamos apenas o SDK para a versão 2.3.3, essa será a única opção.
Também é necessário escolher o tamanho para o SD Card e o tamanho de tela. essas opções podem ficar como no screenshot abaixo.
Depois de criado o AVD, basta selecioná-lo na lista e clicar em "Start..."
Na próxima tela, existem opções para mudar a escala da tela do emulador e para apagar os dados do usuário, gravados em uma execução anterior (Wipe user data)
Esse é o emulator android, com o display touchscreen à esquerda e um teclado "físico" à direita.
O próximo passo para montar o ambiente de desenvolvimento android é a instalação do ADT (Android Development Tools)
O ADT é um plugin do eclipse que facilita a criação de projetos android e o debug das aplicações.
Para instalar o plugin, abra o Eclipse (neste minicurso estou usando a última versão, indigo). No menu "Help", clique em "Install New Software". Então, clique em "Add..." e adicione a seguinte URL:
https://dl-ssl.google.com/android/eclipse/
Após isso, basta selecionar o plugin "Developer Tools" e clicar em "Next" para que seja completada a instalação do ADT.
Com o ambiente de desenvolvimento android configurado, é hora de dar o primeiro passo, fazendo o clássico "HelloWorld"
Passo 1 – Criando o projeto no Eclipse
Abra o Eclipse, vá até File > New > Project
Na tela que aparecer, escolha “Android Project” e clique em “Next”.
Após isso, irá aparecer a tela com as configurações de seu projeto android.
Nesta tela, você precisa inserir os seguintes dados:
Depois disso, basta clicar em “Finish”.
Passo 2 – Imprimindo um texto
Após isso, será criado um novo projeto e dentro dele, na pasta src/android.helloworld/ você encontrará um arquivo .java com o nome da Activity que você colocou no passo anterior. No nosso caso, "HelloWorldActivity"
Para fazer a sua aplicação imprimir um texto na tela, modifique este arquivo da seguinte forma:
package android.helloworld;
import android.app.Activity;
import android.os.Bundle;
import android.widget.TextView;
public class HelloWorldActivity extends Activity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView view = new TextView(this);
view.setText("Hello, Android");
setContentView(view);
}
}
Passo 3 – Rodando a aplicação no emulador
Para rodar nosso recém criado programa no emulador do google, vá até “Run” > Run as “Android Application”. Uma instância do emulador será criada, com o nosso “HelloWorld” rodando.
Agora que conseguimos rodar nossa primeira aplicação "HelloWorld", vamos estudar as ténicas para a construção de uma aplicação android real.
O nosso objetivo será criar uma aplicação simples, que irá conter um campo onde o usuário poderá entrar com uma nota (um texto) uma lista com os textos previamente guardados.
O layout será como na figura abaixo:
Em android, existem duas formas de se definir o layout de uma tela:
Seguir a segunda forma é uma boa prática, pois ajuda o desenvolvedor a separar o código da UI da lógica do aplicativo.
Para armazenar os arquivos XML citados, já existe um folder dedicado no projeto android, em res/layout.Desse diretório já contém um XML criado por padrão, chamado main.xml, com o conteúdo abaixo:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
<TextView
android:layout_width="fill_parent" a
android:layout_height="wrap_content"
android:text="@string/hello"
/>
</LinearLayout>
No arquivo main.xml, temos contato com os primeiros elementos de um arquivo de layout XML:
Para criar um layout parecido com o que queremos, iremos inserir outros dois elementos:
Assim, o nosso novo XML:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
>
<EditText
android:id="@+id/edit_box"
android:layout_width="330px"
android:layout_height="wrap_content"
android:text=""
>
</EditText>
<Button
android:id="@+id/insert_button"
android:layout_width="150px"
android:layout_height="wrap_content"
android:text="Inserir"
>
</Button>
</LinearLayout>
<ListView
android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
>
</ListView>
</LinearLayout>
Para que a nossa aplicação tenha o layout definido pelo arquivo XML, é preciso carregá-lo.
Isso é feito através da função setContentView(), como no código abaixo:
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
O parâmetro R.layout.main indica que o arquivo de layout a ser carregado é o main.xml. (Se o se arquivo se chamar abobrinha.xml, o parâmetro deverá ser R.layout.abobrinha)
Agora é a hora de conhecer uma das mais importantes classes de uma aplicação Android: A classe Activity.
Uma Activity é basicamente uma classe gerenciadora de UI (Interface com o usuário). Todo aplicativo android começa por uma Activity.
Ou seja, quando uma aplicação android é executada, na verdade é a sua Activity principal que é lançada.
A primeira coisa que deve-se conhecer sobre a Activity é o seu ciclo de vida, ilustrado no diagrama a seguir:
Os Content Providers são parte importantíssima da arquitetura de um sistema android. É responsabilidade deles prover às aplicações o conteúdo que elas precisam para funcionar, ou seja, os dados.
Mas por que são realmente necessários?
As aplicações poderiam muito bem acessar diretamente um banco de dados, por exemplo. Porém, é uma boa prática tornar o modo como os dados são gravados transparente à aplicação. Dessa forma, a aplicação pode manter o foco nas interações com o usuário.
Além disso, essa técnica permite a criação de Shared Content Providers, que são providers “públicos” que podem ser acessados por várias aplicações. Por exemplo, existe o content provider de SMS/MMS que permite a qualquer aplicação ler as mensagens recebidas por um telefone celular.
E como é feita a comunicação entre Content Providers e Aplicações?
Através de uma Uri
Toda a comunicação entre aplicações e providers é feita através dos métodos da interface ContentProvider, que sempre recebem um objeto Uri como parâmetro. O formato da Uri é definido pelo content provider. Por exemplo, a Uri content://sms/inbox acessa as mensagens de inbox no Content Provider de SMS. Falaremos um pouco mais sobre as Uris a seguir, mas primeiro, vamos conhecer os métodos que usaremos para enviá-las para o provider:
query(Uri, String[], String, String[], String)- usado para recuperar dados.insert(Uri, ContentValues) – usado para inserir dados.update(Uri, ContentValues, String, String[]) – usado para atualizar dados.delete(Uri, String, String[]) – usado para deletar dados.getType(Uri) – usado para obter o MIME type de certo dado.Depois dessa rápida introdução, vamos colocar a mão na massa.
Iremos criar um content provider para o QuickNotes, que servirá para gravar e recuperar as anotações do usuário, da seguinte forma:
Intencionalmente coloquei a caixa que define como o provider irá gravar os dados para mostrar que isso é irrelevante para a aplicação.
A estrutura das URIs
Uma Uri usada para acessar Content Provider segue o formato:
content://<authority>/<parametro1>/<parametro2>/…/<parametroN>
Onde authority é o “nome” do provider, e os parâmetros são aqueles definidos pelo provider. Por exemplo, a seguinte Uri:
content://sms/conversations/10
Acessa o Content Provider de SMS, e seleciona a conversation de Id número 10.
Criando um Content Provider
Para criar seu próprio content provider, é preciso fazer 2 coisas:
Vamos começar criando a classe QuickNotesProvider:
package android.helloworld;
import android.content.ContentProvider;
import android.net.Uri;
import android.content.ContentValues;
import android.database.Cursor;
public class QuickNotesProvider extends ContentProvider {
// Aqui definimos os formatos possíveis de Uri que
// o nosso provider irá aceitar.
public static final Uri CONTENT_URI = Uri
.parse("content://android.helloworld.quicknotesprovider");
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
return 0;
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
return null;
}
@Override
public boolean onCreate() {
return false;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
return null;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}
Agora, vamos registrar o nosso provider no AndroidManifest, adicionando a seguinte linha entre as tags <application …> e </application>
<provider android:authorities="android.helloworld.quicknotesprovider" android:name=".QuickNotesProvider"/>
Acessando o banco de dados
Com o Content Provider criado, vamos implementar os métodos de acesso e gravação de dados usando o SQLite.
Para isso, usaremos a classe SQLiteOpenHelper para gerenciar a conexão com o banco de dados. Para usá-la, é preciso criar uma subclasse implementando os métodos abaixo:
Implementando os métodos, temos o seguinte Content Provider:
package android.helloworld;
import java.util.HashMap;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.Context;
import android.content.UriMatcher;
import android.net.Uri;
import android.provider.BaseColumns;
import android.util.Log;
import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.database.sqlite.SQLiteQueryBuilder;
public class QuickNotesProvider extends ContentProvider {
// Authority do nosso provider, a ser usado nas Uris.
public static final String AUTHORITY = "android.helloworld.quicknotesprovider";
// Nome do arquivo que irá conter o banco de dados.
private static final String DATABASE_NAME = "quicknotes.db";
// Versao do banco de dados.
// Este valor é importante pois é usado em futuros updates do DB.
private static final int DATABASE_VERSION = 1;
// Nome da tabela que irá conter as anotações.
private static final String NOTES_TABLE = "notes";
// 'Id' da Uri referente às notas do usuário.
private static final int NOTES = 1;
// Tag usada para imprimir os logs.
public static final String TAG = "QuickNotesProvider";
// Instância da classe utilitária
private DBHelper mHelper;
// Uri matcher - usado para extrair informações das Uris
private static final UriMatcher mMatcher;
private static HashMap<String, String> mProjection;
static {
mProjection = new HashMap<String, String>();
mProjection.put(Notes.NOTE_ID, Notes.NOTE_ID);
mProjection.put(Notes.TEXT, Notes.TEXT);
}
static {
mMatcher = new UriMatcher(UriMatcher.NO_MATCH);
mMatcher.addURI(AUTHORITY, NOTES_TABLE, NOTES);
}
/////////////////////////////////////////////////////////////////
// Métodos overrided de ContentProvider //
/////////////////////////////////////////////////////////////////
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
SQLiteDatabase db = mHelper.getWritableDatabase();
int count;
switch (mMatcher.match(uri)) {
case NOTES:
count = db.delete(NOTES_TABLE, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("URI desconhecida " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
@Override
public String getType(Uri uri) {
switch (mMatcher.match(uri)) {
case NOTES:
return Notes.CONTENT_TYPE;
default:
throw new IllegalArgumentException("URI desconhecida " + uri);
}
}
@Override
public Uri insert(Uri uri, ContentValues values) {
Log.i(TAG, "insert op");
switch (mMatcher.match(uri)) {
case NOTES:
SQLiteDatabase db = mHelper.getWritableDatabase();
long rowId = db.insert(NOTES_TABLE, Notes.TEXT, values);
if (rowId > 0) {
Uri noteUri = ContentUris.withAppendedId(Notes.CONTENT_URI, rowId);
getContext().getContentResolver().notifyChange(noteUri, null);
return noteUri;
}
default:
throw new IllegalArgumentException("URI desconhecida " + uri);
}
}
@Override
public boolean onCreate() {
mHelper = new DBHelper(getContext());;
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// Aqui usaremos o SQLiteQueryBuilder para construir
// a query que será feito ao DB, retornando um cursor
// que enviaremos à aplicação.
SQLiteQueryBuilder builder = new SQLiteQueryBuilder();
SQLiteDatabase database = mHelper.getReadableDatabase();
Cursor cursor;
switch (mMatcher.match(uri)) {
case NOTES:
// O Builer receberá dois parametros: a tabela
// onde será feita a busca, e uma projection -
// que nada mais é que uma HashMap com os campos
// que queremos recuperar do banco de dados.
builder.setTables(NOTES_TABLE);
builder.setProjectionMap(mProjection);
break;
default:
throw new IllegalArgumentException("URI desconhecida " + uri);
}
cursor = builder.query(database, projection, selection,
selectionArgs, null, null, sortOrder);
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
SQLiteDatabase db = mHelper.getWritableDatabase();
int count;
switch (mMatcher.match(uri)) {
case NOTES:
count = db.update(NOTES_TABLE, values, selection, selectionArgs);
break;
default:
throw new IllegalArgumentException("URI desconhecida " + uri);
}
getContext().getContentResolver().notifyChange(uri, null);
return count;
}
/////////////////////////////////////////////////////////////////
// Inner Classes utilitárias //
/////////////////////////////////////////////////////////////////
public static final class Notes implements BaseColumns {
public static final Uri CONTENT_URI = Uri.parse("content://"
+ QuickNotesProvider.AUTHORITY + "/notes");
public static final String CONTENT_TYPE = "vnd.android.cursor.dir/vnd.jwei512.notes";
public static final String NOTE_ID = "_id";
public static final String TEXT = "text";
}
private static class DBHelper extends SQLiteOpenHelper {
DBHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
/* O método onCreate é chamado quando o provider é executado pela
* primeira vez, e usado para criar as tabelas no database
*/
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("CREATE TABLE " + NOTES_TABLE + " (" +
Notes.NOTE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
Notes.TEXT + " LONGTEXT" + ");");
}
/* O método onUpdate é invocado quando a versão do banco de dados
* muda. Assim, é usado para fazer adequações para a aplicação
* funcionar corretamente.
*/
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// Como ainda estamos na primeira versão do DB,
// não precisamos nos preocupar com o update agora.
}
}
}
Com o Provider pronto, vamos começar a interagir com ele a partir de nossa UI.
Vamos começar inserindo uma anotação do usuário no banco de dados. Para fazer isso, o primeiro passo é adicionar um Listener ao botão ‘Inserir’, da seguinte forma:
Button insertButton = (Button)findViewById(R.id.insert_button); insertButton.setOnClickListener(mInsertListener);
Este código deve ser colocado dentro do método onCreate.
E agora, criando o objeto mInsertListener. Ele precisa ser um objeto que implementa a interface OnClickListener,. Assim, precisamos implementar o método onClick(), que será chamado assim que o usuário pressionar o botão.
// Definindo um OnClickListener para o botão "Inserir"
private OnClickListener mInsertListener = new OnClickListener() {
public void onClick(View v) {
EditText editBox = (EditText)findViewById(R.id.edit_box);
addNote(editBox.getText().toString());
editBox.setText("");
}
};
No código anterior, eu fiz uma chamada a um método que ainda não está implementado – o método addNote(), que recebe um String que será inserida no banco de dados. Ele será o método responsável por efetivamente “conversar” com o content provider. Vamos implementá-lo:
/*
* Método responsável por inserir um registro no content provider
*/
protected void addNote(String text) {
ContentValues values = new ContentValues();
values.put(QuickNotesProvider.Notes.TEXT, text);
getContentResolver().insert(
QuickNotesProvider.Notes.CONTENT_URI, values);
}
Agora, aprenderemos a usar uma ListActivity para mostrar ao usuario uma lista com as anotações que ele inseriu em nossa aplicação QuickNotes.
ListActivity? O que é isso?
A ListActivity é uma classe filha da Activity cujo objetivo é mostrar ao usuário uma Lista (uma ListView). Em suma, é uma Activity com alguns métodos para gerenciamento de listas, criada com o intuito de facilitar a criação de telas com essa configuração, muito comuns nas aplicações android.
O primeiro passo para criar uma Activity desse tipo é assegurar que o layout XML a ser usado por ela possui um elemento ListView (afinal, estamos criando uma ListActivity!). Além disso, é preciso que seu id seja “@android:id/list”.
Depois disso, o próximo é fazer a nossa classe MainActivity ser uma classe filha da ListActivity:
public class MainActivity extends ListActivity
E então, inicializar a ListView no método onCreate. Mas para isso precisaremos de um Adapter.
Adapters são classes responsáveis por fazer o que é chamado de “bind“: Receber os dados de um Cursor (ou de alguma outra fonte de dados) e colocá-los nos seus respectivos lugares no layout da Activity.
Para Activity’s complexas, tipicamente são criadas subclasses da classe CursorAdapter (Adapter dedicado a tratar cursores). No nosso caso, em que temos um layout bastante simples, é suficiente o uso do SimpleCursorAdapter.
No código abaixo eu mostro como usar este adapter, com uma explicação sobre seus parâmetros:
ListAdapter adapter = new SimpleCursorAdapter(
// O primeiro parametro eh o context.
this,
// O segundo, o layout de cada item.
R.layout.list_item,
// O terceiro parametro eh o cursor que contem os dados
// a serem mostrados
mCursor,
// o quarto parametro eh um array com as colunas do
// cursor que serao mostradas
new String[] {QuickNotesProvider.Notes.TEXT},
// o quinto parametro eh um array (com o mesmo
// tamanho do anterior) com os elementos que
// receberao os dados.
new int[] {R.id.text});
setListAdapter(adapter);
Um dos parâmetros recebidos pelo contrutor da SimpleCursorAdapter é o layout dos itens da lista, que definimos da seguinte forma:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/text"
android:textSize="16sp"
android:textStyle="bold"
android:textColor="#FFFFFF"
android:layout_width="fill_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
E por fim, a nossa Activity principal fica assim:
package android.helloworld;
import android.helloworld.R;
import android.app.ListActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListAdapter;
import android.widget.SimpleCursorAdapter;
import android.content.ContentValues;
import android.content.Intent;
import android.database.Cursor;
public class MainActivity extends ListActivity {
private static final String TAG = "QuickNotesMainActivity";
private Cursor mCursor;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Log.d(TAG, "Criando a MainActivity");
setContentView(R.layout.main);
Button insertButton = (Button)findViewById(R.id.insert_button);
insertButton.setOnClickListener(mInsertListener);
// adicionando um 'Hint' ao Editbox.
EditText editBox = (EditText)findViewById(R.id.edit_box);
editBox.setHint("Nova nota...");
mCursor = this.getContentResolver().
query(QuickNotesProvider.Notes.CONTENT_URI, null, null, null, null);
ListAdapter adapter = new SimpleCursorAdapter(
// O primeiro parametro eh o context.
this,
// O segundo, o layout de cada item.
R.layout.list_item,
// O terceiro parametro eh o cursor que contem os dados
// a serem mostrados
mCursor,
// o quarto parametro eh um array com as colunas do cursor
// que serao mostradas
new String[] {QuickNotesProvider.Notes.TEXT},
// o quinto parametro eh um array (com o mesmo tamanho
// do anterior) com os elementos que receberao
// os dados.
new int[] {R.id.text});
setListAdapter(adapter);
}
/*
* Definindo um OnClickListener para o botão "Inserir"
*/
private OnClickListener mInsertListener = new OnClickListener() {
public void onClick(View v) {
EditText editBox = (EditText)findViewById(R.id.edit_box);
addNote(editBox.getText().toString());
editBox.setText("");
}
};
/*
* Método responsável por inserir um registro no content provider
*/
protected void addNote(String text) {
ContentValues values = new ContentValues();
values.put(QuickNotesProvider.Notes.TEXT, text);
getContentResolver().insert(
QuickNotesProvider.Notes.CONTENT_URI, values);
}
}
O projeto completo encontra-se para download em felipesilveira.com.br/minicurso-android/QuickNotes.rar