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
(ver mais pormenores aqui e aqui)