Neste artigo, apresenta-se uma comunicação http simples para fazer login num servidor Web. A comunicação é feita dentro de uma thread própria, utilizando a classe AsyncTask do Android.
A comunicação HTTP no Android deve ser feita dentro de uma thread para evitar que a aplicação fique “presa” à espera da resposta do servidor, não permitindo, durante esse tempo, que o utilizador interaja com a mesma.
Para além das classes HttpRequest e HttpData (a primeira implementa os métodos de invocação dos comandos GET e POST; a segunda gere a resposta do servidor que pode ser um bloco de texto ou um ficheiro binário) que foram utilizadas no último projeto, Utiliza-se também a classe PostData que permite fazer pedidos http diferentes, utilizando a mesma thread, e tratar as respetivas respostas.
Segue-se o código do ficheiro activity_login.xml, com o formulário de login:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".Login" android:orientation="vertical" > <TextView android:text="Login" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="48sp" /> <TextView android:text="Username" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="16sp" android:paddingTop="20sp" /> <EditText android:id="@+id/user" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="24sp" /> <TextView android:text="Password" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="16sp" android:paddingTop="20sp" /> <EditText android:id="@+id/pass" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="24sp" android:inputType="textPassword" /> <Button android:id="@+id/login" android:text="Login" android:layout_width="fill_parent" android:layout_height="wrap_content" android:textSize="24sp" android:layout_marginTop="20sp" /> </LinearLayout>
Segue-se o código da classe HttpRequest, com os métodos de comunicação HTTP:
package com.se.login; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.NameValuePair; import org.apache.http.client.ClientProtocolException; import org.apache.http.client.HttpClient; import org.apache.http.client.entity.UrlEncodedFormEntity; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.BasicHttpParams; import org.apache.http.params.CoreProtocolPNames; import org.apache.http.params.HttpConnectionParams; import org.apache.http.params.HttpParams; public class HttpRequest { public HttpClient httpclient; public HttpRequest() { HttpParams httpParameters = new BasicHttpParams(); // Set the timeout in milliseconds until a connection is established. // The default value is zero, that means the timeout is not used. int timeoutConnection = 30000; HttpConnectionParams.setConnectionTimeout(httpParameters, timeoutConnection); // Set the default socket timeout (SO_TIMEOUT) // in milliseconds which is the timeout for waiting for data. int timeoutSocket = 60000; HttpConnectionParams.setSoTimeout(httpParameters, timeoutSocket); httpclient = new DefaultHttpClient(httpParameters); httpclient.getParams().setParameter(CoreProtocolPNames.PROTOCOL_VERSION, HttpVersion.HTTP_1_1); } // Faz um pedido GET public HttpData get(String url){ HttpData dados = new HttpData(); HttpGet request = new HttpGet(); try { request.setURI(new URI(url)); HttpResponse resp = httpclient.execute(request); dados.set(resp); } catch (URISyntaxException e1) { e1.printStackTrace(); } catch (ClientProtocolException e1) { e1.printStackTrace(); } catch (Exception e1) { e1.printStackTrace(); } return dados; } public HttpData getImage(String url, List<NameValuePair> vars) { HttpData dados = new HttpData(); HttpPost request = new HttpPost(url); try { request.setEntity(new UrlEncodedFormEntity(vars)); HttpResponse resp = httpclient.execute(request); dados.setImage(resp); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return dados; } // Faz um pedido via POST, com variáveis public HttpData post(String url, List<NameValuePair> vars){ HttpData dados = new HttpData(); HttpPost request = new HttpPost(url); try { request.setEntity(new UrlEncodedFormEntity(vars)); HttpResponse resp = httpclient.execute(request); dados.set(resp); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } return dados; } // Faz um upload de dados binários via POST public HttpData post(String url, String name, String filename, byte[] bin, String type){ HttpData dados = new HttpData(); HttpPost request = new HttpPost(url); SimpleMultipartEntity mpe = new SimpleMultipartEntity(); mpe.addPart(name, filename, bin, type); request.setEntity(mpe); HttpResponse resp = null; try { resp = httpclient.execute(request); } catch (ClientProtocolException e1) { e1.printStackTrace(); } catch (Exception e1) { e1.printStackTrace(); } dados.set(resp); return dados; } }
Segue-se o código da classe HttpData, onde ficam os resultados das consultas HTTP:
package com.se.login; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.util.Hashtable; import org.apache.http.Header; import org.apache.http.HttpResponse; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.util.Log; public class HttpData { public String content; public Hashtable<String,String> cookies; public Hashtable<String,String> headers; public int statusCode; public Bitmap imagem; public HttpData() { content = ""; imagem = null; cookies = new Hashtable<String,String>(); headers = new Hashtable<String,String>(); statusCode = 0; } public void set(HttpResponse resp) { if(resp == null) return; BufferedReader in = null; try { in = new BufferedReader(new InputStreamReader(resp.getEntity().getContent())); StringBuffer sb = new StringBuffer(""); String line = ""; String NL = System.getProperty("line.separator"); while ((line = in.readLine()) != null) { if(sb.length() != 0) sb.append(NL); sb.append(line); } in.close(); content = sb.toString(); if(content.startsWith("")) content = content.substring(3); Log.d("LOG", "HttpData.set(), tamanho de content: " + content.length()); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } Header[] hds = resp.getAllHeaders(); for(Header h: hds) { headers.put(h.getName(), h.getValue()); if (h.getName().equals("set-cookie")) cookies.put(h.getName(), h.getValue()); } statusCode = resp.getStatusLine().getStatusCode(); Log.d("NET", "statusCode = " + statusCode); } public void setImage(HttpResponse resp) { if(resp == null) return; InputStream in = null; try { in = resp.getEntity().getContent(); imagem = BitmapFactory.decodeStream(in); Log.d("LOG", "HttpData.setImage(), tamanho de content: " + content.length()); } catch (IllegalStateException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { if (in != null) { try { in.close(); } catch (IOException e) { e.printStackTrace(); } } } Header[] hds = resp.getAllHeaders(); for(Header h: hds) { headers.put(h.getName(), h.getValue()); if (h.getName().equals("set-cookie")) cookies.put(h.getName(), h.getValue()); } statusCode = resp.getStatusLine().getStatusCode(); Log.d("NET", "statusCode = " + statusCode); } }
Segue-se o código da classe SimpleMultipartEntity, para poder submeter ficheiros binários:
package com.se.login; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Random; import org.apache.http.Header; import org.apache.http.HttpEntity; import org.apache.http.message.BasicHeader; import android.util.Log; public class SimpleMultipartEntity implements HttpEntity { // Original: http://blog.rafaelsanches.com/2011/01/29/upload-using-multipart-post-using-httpclient-in-android/ // Modified by: Alex Banha private final static char[] MULTIPART_CHARS = "-_1234567890abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" .toCharArray(); private String boundary = null; ByteArrayOutputStream out = new ByteArrayOutputStream(); boolean isSetLast = false; boolean isSetFirst = false; public SimpleMultipartEntity() { final StringBuffer buf = new StringBuffer(); final Random rand = new Random(); for (int i = 0; i < 30; i++) { buf.append(MULTIPART_CHARS[rand.nextInt(MULTIPART_CHARS.length)]); } this.boundary = buf.toString(); } public void writeFirstBoundaryIfNeeds(){ if(!isSetFirst){ try { out.write(("--" + boundary + "\r\n").getBytes()); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } } isSetFirst = true; } public void writeLastBoundaryIfNeeds() { if(isSetLast){ return ; } try { out.write(("\r\n--" + boundary + "--\r\n").getBytes()); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } isSetLast = true; } public void addPart(final String key, final String value) { writeFirstBoundaryIfNeeds(); try { out.write(("Content-Disposition: form-data; name=\"" +key+"\"\r\n").getBytes()); out.write("Content-Type: text/plain; charset=UTF-8\r\n".getBytes()); out.write("Content-Transfer-Encoding: 8bit\r\n\r\n".getBytes()); out.write(value.getBytes()); out.write(("\r\n--" + boundary + "\r\n").getBytes()); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } } public void addPart(final String key, final String fileName, final InputStream fin){ addPart(key, fileName, fin, "application/octet-stream"); } public void addPart(final String key, final String fileName, final InputStream fin, String type){ writeFirstBoundaryIfNeeds(); try { type = "Content-Type: "+type+"\r\n"; out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes()); out.write(type.getBytes()); out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); final byte[] tmp = new byte[4096]; int l = 0; while ((l = fin.read(tmp)) != -1) { out.write(tmp, 0, l); } out.flush(); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } finally { try { fin.close(); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } } } public void addPart(final String key, final String fileName, byte[] dados, String type){ writeFirstBoundaryIfNeeds(); try { type = "Content-Type: "+type+"\r\n"; out.write(("Content-Disposition: form-data; name=\""+ key+"\"; filename=\"" + fileName + "\"\r\n").getBytes()); out.write(type.getBytes()); out.write("Content-Transfer-Encoding: binary\r\n\r\n".getBytes()); out.write(dados); out.flush(); } catch (final IOException e) { Log.e("LOG", e.getMessage(), e); } } public void addPart(final String key, final File value) { try { addPart(key, value.getName(), new FileInputStream(value)); } catch (final FileNotFoundException e) { Log.e("LOG", e.getMessage(), e); } } public long getContentLength() { writeLastBoundaryIfNeeds(); return out.toByteArray().length; } public Header getContentType() { return new BasicHeader("Content-Type", "multipart/form-data; boundary=" + boundary); } public boolean isChunked() { return false; } public boolean isRepeatable() { return false; } public boolean isStreaming() { return false; } public void writeTo(final OutputStream outstream) throws IOException { outstream.write(out.toByteArray()); } public Header getContentEncoding() { return null; } public void consumeContent() throws IOException, UnsupportedOperationException { if (isStreaming()) { throw new UnsupportedOperationException( "Streaming entity does not implement #consumeContent()"); } } public InputStream getContent() throws IOException, UnsupportedOperationException { return new ByteArrayInputStream(out.toByteArray()); } }
Segue-se o código da classe PostData, com os dados dos pedidos HTTP (URL, variáveis e binários):
package com.se.login; import java.util.List; import org.apache.http.NameValuePair; public class PostData { public String url; public List<NameValuePair> dados; byte[] binario; public PostData(String url, List<NameValuePair> dados, byte[] binario){ this.url = url; this.dados = dados; this.binario = binario; } }
Segue-se o código do ficheiro AndroidManifest.xml, com as permissões para acesso à Internet:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.se.login" > <uses-permission android:name="android.permission.INTERNET" /> <application android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <activity android:name=".Login" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> </manifest>
Segue-se o código da classe principal, a clase Login.java:
package com.se.login; import android.os.AsyncTask; import android.app.Activity; import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.Toast; import org.apache.http.NameValuePair; import org.apache.http.message.BasicNameValuePair; import java.util.ArrayList; import java.util.List; public class Login extends Activity { EditText user, pass; Button login; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); user = (EditText) findViewById(R.id.user); pass = (EditText) findViewById(R.id.pass); login = (Button) findViewById(R.id.login); login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { fazLogin(); } }); } public void fazLogin() { String sUser = user.getText().toString(); String sPass = pass.getText().toString(); String url = "http://escola.w3.pt/se/_files/login.php"; List<NameValuePair> dados = new ArrayList<NameValuePair>(); dados.add(new BasicNameValuePair("usr", sUser)); dados.add(new BasicNameValuePair("psw", sPass)); new Sincro().execute(new PostData(url, dados, null)); } public class Sincro extends AsyncTask<PostData, Integer, HttpData> { @Override protected HttpData doInBackground(PostData... params) { Log.i("LOGGER", "Sincro Starting..."); HttpData dados = null; try { HttpRequest req = new HttpRequest(); dados = req.post(params[0].url, params[0].dados); } catch (Exception e) { e.printStackTrace(); } return dados; } protected void onProgressUpdate(Integer... progress) { } @Override protected void onPostExecute(HttpData dados) { Log.i("LOGGER", "...Done"); Toast.makeText(Login.this, dados.content, Toast.LENGTH_LONG).show(); } } }
Segue-se o código do ficheiro login.php que está no servidor Web:
<?php if($_POST['usr']=='se' && $_POST['psw']=='xpto') echo 'OK'; else echo 'NOT OK'; ?>
Nota: a partir da versão 23 do sdk, o Android deixou de suportar o HttpClient. A partir desta versão, há as opções seguintes, para comunicar pela rede:
- usar a classe URLConnection
- compilar para a versão 22 do sdk (compile ‘com.android.support:appcompat-v7:22.2.0’)
- ou, no AndroidStudio, com a versão 23, colocar no gradle: android { useLibrary ‘org.apache.http.legacy’ }
- ou, no Eclipse, com a versão 23, adicionar a biblioteca org.apache.http.legacy.jar, que está em Android/Sdk/platforms/android-23/optional, às dependências do projeto
- importar a biblioteca HttpClient jar para o projeto