Neste capítulo do curso de Android iremos aprender como acessar um servidor utilizando HTTP.
Iremos utilizar este servidor para fazer backup das notas criadas pela nossa aplicação QuickNotes.
Se você não estava acompanhando o Curso de Android, faça o download do projeto aqui, pois as alterações serão feitas neste projeto.
Clientes HTTP em Android
Em Android temos dois clientes HTTP:
- Apache HTTP Client
- HttpURLConnection
O Google aconselha o uso de HttpURLConnection desde a versão 2.3 (Gingerbread)
Usaremos o HTTPUrlConnection para enviar as notas, que no nosso exemplo são armazenadas no banco de dados local, para o servidor remoto. Abaixo segue a implementação:
String urlParameters = "nota=" + URLEncoder.encode(note,"UTF-8"); String request = "http://tests.felipesilveira.com.br/android-core/insert.php"; URL url = new URL(request); // Criando a instância de HttpURLConnection responsável por acessar a rede HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setInstanceFollowRedirects(false); // Vamos enviar a requisição via POST connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("charset", "utf-8"); connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length)); connection.setUseCaches (false); // Criando um DataOutputStream que será usado para receber a resposta do servidor. DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.writeBytes(urlParameters); wr.flush(); wr.close(); String response = ""; Scanner inStream = new Scanner(connection.getInputStream()); while (inStream.hasNextLine()) { response += (inStream.nextLine()); } // Removendo possiveis quebras de linha response.replaceAll("\n", ""); Log.v("DataSync", "Resposta do server=("+response+")"); inStream.close(); connection.disconnect();
Para fazer a sincronização com nosso servidor remoto na aplicação QuickNotes, vamos seguir dois passos:
- Criar uma flag no banco de dados, para termos controle do que já foi enviado.
- Usar HTTPUrlConnection para enviar os dados via POST para o servido.
Passo 1 – Controle de sincronização
Para criar uma coluna extra na tabela do nosso banco de dados, basta alterar o método onCreate da classe QuickNotesProvider$DBHelper
@Override public void onCreate(SQLiteDatabase db) { db.execSQL("CREATE TABLE " + NOTES_TABLE + " (" + Notes.NOTE_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," + Notes.SYNCED + " INTEGER," + Notes.TEXT + " LONGTEXT" + ");");
Após a alteração no DBHelper, é necessário criar a constante SYNCED na classe QuickNotesProvider$Notes, que fica assim
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.quicknotes"; public static final String NOTE_ID = "_id"; public static final String TEXT = "text"; public static final String SYNCED = "synced"; }
Assim, a nossa classe QuickNotesProvider deve ficar assim:
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 ira conter o banco de dados. private static final String DATABASE_NAME = "quicknotes.db"; // Versao do banco de dados. // Este valor eh importante pois sera usado em futuros updates do DB. private static final int DATABASE_VERSION = 1; // Nome da tabela que ira conter as anotacoes. private static final String NOTES_TABLE = "notes"; // 'Id' da Uri referente as notas do usuario. private static final int NOTES = 1; // Tag usada para imprimir os logs. public static final String TAG = "QuickNotesProvider"; // Instancia da classe utilitaria private DBHelper mHelper; // Uri matcher - usado para extrair informacoes 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); } ///////////////////////////////////////////////////////////////// // Metodos 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 sera feito ao DB, retornando um cursor // que enviaremos a aplicacao. SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); SQLiteDatabase database = mHelper.getReadableDatabase(); Cursor cursor; switch (mMatcher.match(uri)) { case NOTES: // O Builer recebera dois parametros: a tabela // onde sera feita a busca, e uma projection - // que nada mais eh 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 utilitarias // ///////////////////////////////////////////////////////////////// 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.quicknotes"; public static final String NOTE_ID = "_id"; public static final String TEXT = "text"; public static final String SYNCED = "synced"; } private static class DBHelper extends SQLiteOpenHelper { DBHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); } /* O metodo onCreate eh chamado quando o provider eh 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.SYNCED + " INTEGER," + Notes.TEXT + " LONGTEXT" + ");"); } /* O metodo onUpdate eh invocado quando a versao do banco de dados * muda. Assim, eh usado para fazer adequacoes para a aplicacao * funcionar corretamente. */ @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { // Como ainda estamos na primeira versao do DB, // nao precisamos nos preocupar com o update agora. } } }
Além das alterações no Content Provider, precisamos alterar o método doInBackground da AsyncTask na MainActivity para passar a gravar as notas com a flag SYNCED = 0, significando que ela ainda não foi enviada ao servidor.
protected Uri doInBackground(String... string) { ContentValues values = new ContentValues(); values.put(QuickNotesProvider.Notes.TEXT, string[0]); // Gravando a flag SYNCED com o valor 0, significando que // a nota ainda não foi enviada ao servidor values.put(QuickNotesProvider.Notes.SYNCED, "0"); return getContentResolver().insert( QuickNotesProvider.Notes.CONTENT_URI, values); }
Passo 2 – Sincronização com o servidor remoto
Com a flag criada, seguimos ao segundo passo que é a criação dos métodos para acesso ao servidor remoto.
Criaremos para isso a class DataSync, que possui dois métodos:
- syncPendingNotes(), responsável por procurar no banco de dados as notas ainda não enviadas, e
- sendPendingNote(), responsável por enviar uma nota ao servidor.
Com a implementação destes métodos, a classe DataSync fica assim:
package android.helloworld; import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.helloworld.QuickNotesProvider; import android.net.Uri; import android.util.Log; import java.io.DataOutputStream; import java.io.IOException; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; import java.net.URLEncoder; import java.util.ArrayList; import java.util.List; import java.util.Scanner; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; public class DataSync { private static final String TAG = "DataSync"; private Context mContext; public DataSync(Context context) { mContext = context; } public void syncPendingNotes() { Log.v(TAG, "Iniciando sincronizacao"); // Procurando no banco de dados as entradas com SYNCED = 0, // o que significa que tal dado ainda nao foi enviado ao // servidor remoto. String columns[] = new String[] { QuickNotesProvider.Notes.TEXT, QuickNotesProvider.Notes.NOTE_ID}; Uri contentUri = QuickNotesProvider.Notes.CONTENT_URI; Cursor cur = mContext.getContentResolver().query(contentUri, columns, QuickNotesProvider.Notes.SYNCED + "=0", null, null ); if (cur.moveToFirst()) { Log.v("DataSync", "Existem notas a serem enviadas."); String note = null; int id = 0; do { // Obtendo o valor do campo text note = cur.getString( cur.getColumnIndex(QuickNotesProvider.Notes.TEXT)); id = cur.getInt( cur.getColumnIndex(QuickNotesProvider.Notes.NOTE_ID)); Log.v("DataSync", "Sincronizando " + note ); if(sendPendingNote(note)) { Log.v("DataSync", "Sincronizacao feita com sucesso!"); // Como a sincronizacao foi bem sucedida, vamos agora marcar a // flag SYNCED com 1. ContentValues values = new ContentValues(); values.put(QuickNotesProvider.Notes.SYNCED, "1"); mContext.getContentResolver().update(contentUri, values, QuickNotesProvider.Notes.NOTE_ID + "=" + id, null); } } while (cur.moveToNext()); } else { Log.v(TAG, "Sem novas notas"); } cur.close(); } public boolean sendPendingNote(String note) { try { String urlParameters = "nota=" + URLEncoder.encode(note,"UTF-8"); String request = "http://tests.felipesilveira.com.br/android-core/insert.php"; URL url = new URL(request); // Criando a instância de HttpURLConnection responsável por acessar a rede HttpURLConnection connection = (HttpURLConnection) url.openConnection(); connection.setDoOutput(true); connection.setDoInput(true); connection.setInstanceFollowRedirects(false); // Vamos enviar a requisição via POST connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); connection.setRequestProperty("charset", "utf-8"); connection.setRequestProperty("Content-Length", "" + Integer.toString(urlParameters.getBytes().length)); connection.setUseCaches (false); // Criando um DataOutputStream que será usado para receber a resposta do servidor. DataOutputStream wr = new DataOutputStream(connection.getOutputStream()); wr.writeBytes(urlParameters); wr.flush(); wr.close(); String response = ""; Scanner inStream = new Scanner(connection.getInputStream()); while (inStream.hasNextLine()) { response += (inStream.nextLine()); } // Removendo possiveis quebras de linha response.replaceAll("\n", ""); Log.v("DataSync", "Resposta do server=("+response+")"); inStream.close(); connection.disconnect(); // Quando a nota eh corretamente salva no servidor, // este responde com "1". return response.equals("1"); } catch (MalformedURLException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } return false; } }
Iniciando a sincronização
Por fim, basta invocar a sincronização no método doInBackground() da AsyncTask que criamos na MainActivity. Porém, só chamaremos o método se houver conectividade com internet – para saber isso basta usar o ConnectivityManager:
ConnectivityManager cm = (ConnectivityManager)getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); if((activeNetwork != null) && activeNetwork.isConnected()) { DataSync sync = new DataSync(MainActivity.this); sync.syncPendingNotes(); }
Integrando o código na MainActivity:
package android.helloworld; import android.app.ListActivity; import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; 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.widget.Toast; 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); Intent i = new Intent(this, WelcomeActivity.class); startActivity(i); 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 é 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 é 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) { AddNoteTask task = new AddNoteTask(); // É necessário passar como parâmetro um array de strings, // que será o parâmetro recebido pelo método doInBackground. // Neste caso, será passado um array com apenas um item: A nova nota task.execute(new String[] { text } ); } private class AddNoteTask extends AsyncTask<String, Void, Uri> { @Override protected Uri doInBackground(String... string) { ContentValues values = new ContentValues(); values.put(QuickNotesProvider.Notes.TEXT, string[0]); // Gravando a flag SYNCED com o valor 0, significando que // a nota ainda não foi enviada ao servidor values.put(QuickNotesProvider.Notes.SYNCED, "0"); Uri inserted = getContentResolver().insert( QuickNotesProvider.Notes.CONTENT_URI, values); ConnectivityManager cm = (ConnectivityManager)getSystemService( Context.CONNECTIVITY_SERVICE); NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); if((activeNetwork != null) && activeNetwork.isConnected()) { DataSync sync = new DataSync(MainActivity.this); sync.syncPendingNotes(); } return inserted; } @Override protected void onPostExecute(Uri result) { // Após a inserção da nota, vamos mostrar um Toast ao usuário Toast toast = Toast.makeText(MainActivity.this, "Nota inserida com sucesso!", Toast.LENGTH_SHORT); toast.show(); } } }
Backend
Para os testes, foi criado um backend no seguinte endereço:
http://tests.felipesilveira.com.br/android-core/
Você pode conferir se a sua nota foi corretamente enviada através do endereço:
http://tests.felipesilveira.com.br/android-core/insert.php

Felipe, primeiramente queria dizer que gostei muito e estou aprendendo muito com esses seus posts. Mas tenho uma duvida, no método doInBackground ele pede um retorno, como posso resolver isso?
Olá Franklin!
Obrigado pelo comentário! Estava faltando uma linha no exemplo, agora já está corrigido.
Oi Felipe,
Parabens pela iniciativa.
Quanto à conexão com banco de dados, é comum utilizar o SQL lite em aplicações mobile? ou só utilizou aqui para facilitar o ensinamento?
Olá Felipe!
Primeiramente, parabéns por este magnífico trabalho e boa vontade em compartilhar seu conhecimento.
Me deparei com um erro nesta fase do projeto, nada complicado, mas fica como dica caso alguém tenha este mesmo problema.
Ao tentar inserir uma nota, a aplicação fechou, com o seguinte erro no LogCat:
“java.lang.SecurityException: ConnectivityService: Neither user nor current process has android.permission.ACCESS_NETWORK_STATE.”
Uma rápida pesquisada, consegui solucionar o problema, adicionando as permissões abaixo ao AndroidManifest.xml:
Grande abraço,
Jefferson
Permissões(Trocar os ? por tag):
?uses-permission android:name=”android.permission.INTERNET” /?
?uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE” /?
?uses-permission android:name=”android.permission.ACCESS_NETWORK_STATE”/?
Gostaria de saber quanto voce cobraria para desenvolver um plugin cordova/phonegap para android para a impressora termica p25 bluebamboo. Existe um demo neste link gostaria de transformalo em um plugin https://github.com/lorensiuswlt/P25Demo
Olá Felipe,
Tenho empresa de rastreamento veicular, temos um app ativo somente de posição dos veiculos…
gostaria de criar algo para interagir o app com meu sistema tanto pra comandos quanto a envio e recebimento de mensagem em texto livre…
Faz esse tipo de projeto?
Abraço
Whats 19-974025947
Olá Felipe, quero te parabenizar pela iniciativa.Apesar de não estar tratado aqui por ser fora do tópico Android, mas gostaria de saber como foi feito o backend para tratar a sincronização das mensagens. Tem algum tutorial que posso seguir para fazê-lo?
Olá bom dia, eu fiz de uma outra maneira, mas não esta dando certo. estou enviando o código onde faço a conexão com o banco e salvo as informações, se vc puder dar uma olhada eu agradeço…
package gp_adm_ti_pc.example.com.grandpneus.Uteis;
import android.net.Uri;
import android.os.AsyncTask;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.URL;
public class AtualizaDadosClienteSite extends AsyncTask {
private String nome;
private String cpf;
private String telefone;
private String cidade;
public void setNome(String nome) {
this.nome = nome;
}
public void setCpf(String cpf) {
this.cpf = cpf;
}
public void setTelefone(String telefone) {
this.telefone = telefone;
}
public void setCidade(String cidade) {
this.cidade = cidade;
}
public String getNome() {
return nome;
}
public String getCpf() {
return cpf;
}
public String getTelefone() {
return telefone;
}
public String getCidade() {
return cidade;
}
@Override
protected String doInBackground(Void… params) {
HttpURLConnection urlConnection = null;
try{
URL url = new URL(“http://www.grandpneus.com.br/www.apk_php.SalvaCliente.php”);
urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setReadTimeout(10000);
urlConnection.setConnectTimeout(15000);
urlConnection.setRequestMethod(“POST”);
urlConnection.setDoInput(true);
urlConnection.setDoOutput(true);
Uri.Builder builder = new Uri.Builder();
builder.appendQueryParameter(“CPF”, getCpf());
builder.appendQueryParameter(“NOME”, getNome());
builder.appendQueryParameter(“TELEFONE”, getTelefone());
builder.appendQueryParameter(“CIDADE”, getCidade());
String query = builder.build().getEncodedQuery();
OutputStream os = urlConnection.getOutputStream();
BufferedWriter writer = new BufferedWriter(
new OutputStreamWriter(os, “UTF-8”));
writer.write(query);
writer.flush();
writer.close();
os.close();
//String result = readStream(urlConnection.getInputStream());
urlConnection.connect();
}
catch (Exception erro){
erro.printStackTrace();
if (urlConnection != null) {
urlConnection.disconnect();
}
}
return null;
}
public static String readStream(InputStream in) {
BufferedReader reader = null;
StringBuilder builder = new StringBuilder();
try {
reader = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = reader.readLine()) != null) {
builder.append(line + “\n”);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return builder.toString();
}
}
Felipe boa tarde
Primeiramente parabens pela iniciativa e pelo trabalho.
Sou iniciante no mundo Android e estou aprendendo muito com seus posts.
Gostaria de solicitar uma ajuda a voce, poderia disponibilizar por email o arquivo PHP da URL http://tests.felipesilveira.com.br/android-core/insert.php
Gostaria de aprender como integrou o Aplicativo com o servidor WEB usando PHP.
Fico no aguardo e obrigado!
Olá Carlos!
O arquivo PHP é bastante simples:
Obrigado!