Login, HTTP e AsyncTask

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)

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *