Author Archives: alex

Novo Guia de Desenvolvimento para Android – II

No seguimento do artigo Novo Guia de Desenvolvimento para Android, e depois de me ter surgido novamente um problema já documentado nesse artigo, exponho aqui a solução para a dificuldade de correr um emulador na configuração descrita.

Esta semana decidi atualizar o sistema (kernel, bibliotecas, gcc, etc) da máquina onde tenho o ambiente de desenolvimento em Linux para Android com o Android Studio. Quando, mais tarde, tentei executar um emulador, recebi a mensagem:

Failed to start emulator: Cannot run program “/usr/local/android-sdk-linux/tools/emulator”

Mas esse programa existia e era executável. E já tinha funcionado imensas vezes anteriormente. Depois de uma pesquisa aturada na net@, confimei as minhas suspeitas: faltavam bibliotecas ao sistema.

Quando instalei, pela primeira vez, o ambiente de desenvolvimento para Android em Linux 64 bits, descobri que era preciso ter suporte para bibliotecas de 32 bits para correr o emulador.
Agora, depois da atualização da máquina, é necessário corrigir o problema de novo.

Para resolver esse problema no Slackware, há uma sequência de instruções do Eric Hameleers que adicionam capacidade multilib para o Slackware x86_64. Na altura, não coloquei aqui as instruções, mas agora vou colocar.

# SLACKVER=current
# cd /usr/local
# mkdir multilib
# cd multilib
# lftp -c "open http://taper.alienbase.nl/mirrors/people/alien/multilib/ ; mirror -c -e ${SLACKVER}"
# cd ${SLACKVER}
# upgradepkg --reinstall --install-new *.t?z
# upgradepkg --install-new slackware64-compat32/*-compat32/*.t?z
# upgradepkg --install-new debug/*.t?z

Para além disso, é necessário instalar suporte para virtualização em hardware. No caso do Slackware, o site seguinte explica o que fazer: KVM and libvirt

Na prática é necessário ir buscar os 3 pacotes seguintes, compilá-los e instalá-los:
yajl
urlgrabber
libvirt

Por fim, é necessário colocar o código seguinte no ficheiro /etc/rc.d/rc.local e executá-lo antes de lançar um emulador:

# start libvirt
if [ -x /etc/rc.d/rc.libvirt ]; then
    /etc/rc.d/rc.libvirt start
fi 

Testa Rede

Esta aplicação testa se a rede está disponível.
Caso não haja uma ligação de rede, a aplicação lança uma caixa de diálogo a informar o utilizador e pergunta-lhe se quer ativar a rede. Se o utilizador anuir, é lançada uma janela do sistema para ativação da rede. Caso contrário a aplicação termina.
Quando o utilizador desliga a janela de sistema, a aplicação volta a avaliar se tem rede ativa.

Para lançar uma caixa de diálogo, é utilizada a classe AlertDialog.Builder. Existem muitas possibilidades gráficas de utilização da classe AlertDialog.Builder.

A função ativaRede2 lança a janela de configuração do sistema para ativação de uma ligação de rede. Para isso utiliza as instruções seguintes:

        Intent i = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
        startActivityForResult(i,codigo);

Quando a janela de sistema de configuração da rede termina, a aplicação retoma a execução na função onActivityResult. Aqui, devemos avaliar o valor do parâmetro requestCode para saber de que Activity acabámos de retornar. O parâmetro requestCode contém o valor da variável codigo que enviámos para a função startActivityForResult.

Código do layout gráfico:

 <RelativeLayout 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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".TestaRede">

    <TextView
        android:textSize="36sp"
        android:text="@string/hello_world"
        android:id="@+id/texto"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        />

</RelativeLayout>

Código da classe principal:

 package com.se.testarede;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.provider.Settings;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;


public class TestaRede extends ActionBarActivity {

    final int REQUEST_WIRELESS = 1;
    final int REQUEST_WIRELESS_2 = 2;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_testa_rede);

        acao_principal();
    }

    void acao_principal() {
        boolean haRede = isNetworkAvailable();
        if(!haRede) {
            ativaRede(REQUEST_WIRELESS);
        } else {
            TextView texto = (TextView)findViewById(R.id.texto);
            texto.setText("A rede já está ativa");
        }
    }

    public boolean isNetworkAvailable() {
        ConnectivityManager connectivityManager = (ConnectivityManager)
                getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo activeNetworkInfo = connectivityManager.getActiveNetworkInfo();
        return activeNetworkInfo != null;
    }

    public void ativaRede(final int codigo) {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setMessage(getResources().getString(R.string.sync_ativar_rede))
                .setCancelable(false)
                .setPositiveButton(getResources().getString(R.string.yes), new DialogInterface.OnClickListener(){
                    public void onClick(DialogInterface dialog, int id) {
                        ativaRede2(codigo);
                    }
                })
                .setNegativeButton(getResources().getString(R.string.no), new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface dialog, int id) {
                        dialog.cancel();
                        finish();
                    }
                });
        AlertDialog alert = builder.create();
        alert.show();
    }

    public void ativaRede2(int codigo) {
        Intent i = new Intent(Settings.ACTION_WIRELESS_SETTINGS);
        startActivityForResult(i,codigo);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        switch(requestCode) {
            case REQUEST_WIRELESS:
                Log.d("LOG", "onActivityResult().REQUEST_WIRELESS");
                acao_principal();
                break;
            case REQUEST_WIRELESS_2:
                // outra operação
                break;
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_testa_rede, menu);
        return true;
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle action bar item clicks here. The action bar will
        // automatically handle clicks on the Home/Up button, so long
        // as you specify a parent activity in AndroidManifest.xml.
        int id = item.getItemId();

        //noinspection SimplifiableIfStatement
        if (id == R.id.action_settings) {
            return true;
        }

        return super.onOptionsItemSelected(item);
    }
}

Código do Android Manifest:

 <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.se.testarede" >

    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".TestaRede"
            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>

Ficheiro strings.xml:

 <resources>
    <string name="app_name">TestaRede</string>
    <string name="hello_world">Olá Mundo!</string>
    <string name="action_settings">Configuração</string>
    <string name="sync_ativar_rede">Esta aplicação precisa de rede para funcionar. Quer ativar a rede agora?</string>
    <string name="yes">Sim</string>
    <string name="no">Não</string>
</resources>

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)

Novo Guia de Desenvolvimento para Android

No início deste blog escrevi o artigo Guia para o desenvolvimento Android – Instalação. Entretanto, a Google lançou um novo ambiente de desenvolvimento para Android e houve outras alterações, nomeadamente na criação de certificados para assinar aplicações. Este artigo apresenta essas alterações.

Lista de pacotes a instalar

A instalação num sistema Linux de 64 bits pode ser complicada.

Nas instruções de instalação do Android Studio para Linux, avisam que pode ser necessário instalar os pacotes ia32-libs, lib32ncurses5-dev, e lib32stc++6 para poder correr aplicações de 32 bits em máquinas de 64 bits. As distribuições de 64 bits de Linux já há muito tempo que não trazem suporte para correr aplicações de 32 bits e estes pacotes têm mesmo que ser instalados à parte, pois as ferramentas do Android estão em 32 bits. 🙁

No meu caso, durante a configuração (Wizard) do Android Studio, recebi a mensagem: unable to run mksdcard sdk tool

Confirmei que a variável de ambiente ANDROID_HOME existia. Confirmei que a diretoria tools estava na PATH e que o comando mksdcard existia. O problema é que esse comando precisa de bibliotecas de 32 bits.

Para resolver esse problema no Slackware, que é a distribuição de Linux que utilizo, segui as instruções do Eric Hameleers para adicionar capacidade multilib para o Slackware x86_64.

Depois de descarregar e instalar os pacotes indicados, já consegui correr o Wizard até ao fim.

(continua…)

Transferir dados entre atividades – II

Neste artigo vamos ver como transferir dados para uma atividade secundária a partir da atividade principal.

O código abaixo invoca uma atividade e passa-lhe dados.

    String conteudo;
    Intent grf = new Intent(this, Grafico.class);
    grf.putExtra("dados", conteudo);
    startActivityForResult(grf, REQUEST_GRAFICO);

No exemplo anterior, a atividade invocada é uma classe com o nome Grafico. Para lançar a atividade bastam a segunda e a quarta linhas anteriores. Na terceira linha, é adicionada uma String (conteudo) ao objeto que vai lançar a nova atividade. Essa String pode ser recolhida, na atividade secundária, com o nome “dados”. Para isso, usa-se o código apresentado abaixo.

public class Grafico extends Activity {
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.grafico);

		Bundle extras = getIntent().getExtras();
	        String dados = extras.getString("dados");
        }
}

Transferir dados entre atividades

Nesta crónica vamos focar a transferência de dados entre a atividade invocada e a atividade invocadora, ou seja, como é que posso receber dados de uma atividade que invoquei.

Para enviar dados de volta para a atividade principal, a melhor forma é através de um Parcel (encomenda ou pacote). Segue um exemplo de uma classe que contém os dados a transferir para a atividade principal.

import android.os.Parcel;
import android.os.Parcelable;

//simple class that just has one member property as an example
public class PacoteDados implements Parcelable {
	String que;
	String[] opcoes;

	public PacoteDados(String que, String[] opcoes) {
		this.que = que;
		this.opcoes = opcoes;
	}

	/* everything below here is for implementing Parcelable */
	public int describeContents() {
		return 0;
	}

	// write your object's data to the passed-in Parcel
	public void writeToParcel(Parcel out, int flags) {
		out.writeString(que);
		out.writeInt(opcoes.length);
		out.writeStringArray(opcoes);
	}

	// this is used to regenerate your object. All Parcelables must have a CREATOR that implements these two methods
	public static final Parcelable.Creator<PacoteDados> CREATOR = new Parcelable.Creator<PacoteDados>() {
		public PacoteDados createFromParcel(Parcel in) {
			return new PacoteDados(in);
		}

		public PacoteDados[] newArray(int size) {
			return new PacoteDados[size];
		}
	};

	// example constructor that takes a Parcel and gives you an object populated with it's values
	private PacoteDados(Parcel in) {
		que = in.readString();
		int n = in.readInt();
		opcoes = new String[n];
		in.readStringArray(opcoes);
	}
}

Na classe anterior, os dados que vão ser transferidos são uma String que e um array de Strings opcoes. A classe deve implementar a interface Parcelable. É possível ter literalmente qualquer tipo de dados, nesta classe, e transferi-los para a atividade principal. A grande vantagem desta solução face a uma serialização é a rapidez.

O primeiro construtor permite fornecer os dados a transferir, no momento da criação de um objeto. O ultimo construtor é invocado pela atividade principal para rececionar os dados. O método writeToParcel deve escrever no Parcel todos os dados necessários para reconstruir os dados enviados: note-se que foi necessário passar o tamanho do array opcoes de forma a poder recriá-lo na receção (ver último construtor).

O objeto CREATOR do tipo Parcelable.Creator<PacoteDados> é obrigatório e é necessário para regenerar o objeto.

Na classe secundária (aquela que vai devolver os dados) o processo de retorno é o seguinte:

		Intent resultIntent = new Intent();
		PacoteDados pacote = new PacoteDados(que, opcSend);
		resultIntent.putExtra("dados", pacote);
		Log.d(TAG, this.toString());
		setResult(Activity.RESULT_OK, resultIntent);
		
		finish();

A invocação e a receção, na classe principal, são apresentadas de seguida:

    // Invocação
    public static final int REQUEST_CRIAR = 4;
    Intent grf = new Intent(this, Criar.class);
    startActivityForResult(grf, REQUEST_CRIAR);

    // Receção
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
        super.onActivityResult(requestCode, resultCode, intent);
        switch(requestCode) {
        case REQUEST_CRIAR:
            if (resultCode == Activity.RESULT_OK) { 
                PacoteDados pacote = (PacoteDados) intent.getParcelableExtra("dados");

                String s = "que: " + pacote.que;
                for(int i=0; i<pacote.opcoes.length; i++)
                	s += ", op" + (i+1) + ": " + pacote.opcoes[i];
                Toast.makeText(this, s, Toast.LENGTH_LONG).show();
        	break;
        }
    }
   

Na receção, o campo que é recolhido de imediato, enquanto os vários campos de opcoes são recolhidos de forma iterativa. No fim é apresentado o conteúdo do retorno numa mensagem do tipo Toast.

Ler uma imagem via HTTP

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. No entanto, neste artigo vamos solicitar uma imagem a um servidor Web dentro da thread principal. É conveniente que a imagem seja pequena (tenha apenas alguns kb) para que a resposta do servidor não faça a aplicação terminar por inatividade. No próximo artigo sobre comunicação HTTP falaremos da criação de uma thread com a classe AsyncTask para implementar uma comunicação HTTP num ambiente mais seguro.

Este exemplo destina-se a apresentar uma classe de comunicação HTTP que permite enviar pedidos via GET ou via POST a um servidor. Para o efeito, são usadas duas 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.

Segue-se o código da classe HttpRequest:

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;
    }
}

A classe HttpData tem o seguinte conteúdo:

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);
    }
}

Esta aplicação tem um layout gráfico relativamente simples:

  • um campo de texto para se colocar o endereço Web de uma imagem
  • um botão para acionar o pedido
  • um campo do tipo ImageView para apresentar a imagem lida da Net.

Segue-se o layout gráfico :

<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=".MainActivity"
	android:orientation="vertical"
	>
	<TextView
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="URL da imagem:"
		/>
	<EditText
		android:id="@+id/url"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:textSize="20sp"
		/>
	<Button
		android:id="@+id/botao"
		android:layout_width="fill_parent"
		android:layout_height="wrap_content"
		android:text="Ler Imagem"
		android:textSize="20sp"
		/>
	<ImageView
		android:id="@+id/imagem"
		android:layout_width="wrap_content"
		android:layout_height="wrap_content"
		/>
</LinearLayout>

Para que a aplicação possa comunicar via HTTP, é necessário adicionar à aplicação a permissão de acesso à Web. No ficheiro Android Manifest adicionar o código seguinte antes da marca <application…>:

<uses-permission android:name="android.permission.INTERNET" />

O layout fica com o aspeto seguinte:
imagem

Por fim, a aplicação principal tem o código abaixo:

package com.example.imagem;

import android.os.Bundle;
import android.os.StrictMode;
import android.os.StrictMode.ThreadPolicy;
import android.app.Activity;
import android.view.Menu;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;

public class MainActivity extends Activity {ImageView imagem;
	EditText url;
	Button botao;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		ThreadPolicy tp = ThreadPolicy.LAX;
		StrictMode.setThreadPolicy(tp);
		super.onCreate(savedInstanceState);
		setContentView(R.layout.main);

		imagem = (ImageView) findViewById(R.id.imagem);
		url = (EditText) findViewById(R.id.url);
		botao = (Button) findViewById(R.id.botao);

		botao.setOnClickListener( new View.OnClickListener() {
			public void onClick(View v) {
				String sUrl = url.getText().toString();
				HttpRequest req = new HttpRequest();
				HttpData dados = req.getImage(sUrl);
				imagem.setImageBitmap(dados.imagem);
			}
		});
	}
}

As duas primeiras linhas da função onCreate servem para permitir a comunicação HTTP dentro da thread principal. São as seguintes:

ThreadPolicy tp = ThreadPolicy.LAX;
StrictMode.setThreadPolicy(tp);

Nas primeiras versões do sistema operativo Android era possível executar um pedido HTTP dentro da thread principal. Atualmente isso gera uma exceção do tipo android.os.NetworkOnMainThreadException. Estas duas linhas permitem uma política  de utilização de threads mais permissiva e evitam a ocorrência da exceção. Num artigo próximo, esta situação será resolvida invocando o pedido HTTP dentro de uma nova thread com a classe AsyncTask.

SQLite em Android

O SQLite é um sistema de gestão de bases de dados autónomo, que funciona de forma diferente dos sistemas cliente-servidor. Em SQLite não existe um servidor que execute queries a pedido dos clientes. No SQLite, a aplicação cliente é responsável por criar a base de dados (BD) caso ela não exista, atualizá-la se a versão instalada da BD for mais antiga que a atual usada pela aplicação e, também manipular os dados.

Portanto, contrariamente aos sistemas cliente-servidor usuais como o MySQL, o Oracle, o SQL Server, etc., a aplicação Android que use SQLite tem que incluir o código SQL de manipulação da estrutura da BD, assim como o código de manipulação dos dados.

Este artigo apresenta um exemplo de uma BD simples com uma única tabela para armazenar livros (autor e título). Segue-se a classe DBAdapter que é usada pela Activity definida mais adiante.

package se.bd;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;

public class DBAdapter
{
private static final String DATABASE_NAME = “Livros”;
private static final int DATABASE_VERSION = 1;

private static final String TABELA_LIVRO =
“CREATE TABLE livro (”
+ “_id INTEGER PRIMARY KEY AUTOINCREMENT, ”
+ “liv_titulo VARCHAR(128), ”
+ “liv_autor VARCHAR(128)”
+ “)”;

private final Context context;

private DatabaseHelper DBHelper;
private SQLiteDatabase db;

public DBAdapter(Context ctx)
{
this.context = ctx;
DBHelper = new DatabaseHelper(context);
}

private static class DatabaseHelper extends SQLiteOpenHelper
{
DatabaseHelper(Context context)
{
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}

@Override
public void onCreate(SQLiteDatabase db)
{
db.execSQL(TABELA_LIVRO);
}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion)
{
db.execSQL(“DROP TABLE IF EXISTS livro”);
onCreate(db);
}
}

//—abre a ligação à BD—
public DBAdapter open() throws SQLException
{
db = DBHelper.getWritableDatabase();
return this;
}

//—fecha a ligação à BD—
public void close()
{
DBHelper.close();
}

//—insere um livro na BD—
public long insereLivro(String titulo, String autor)
{
ContentValues registo = new ContentValues();
registo.put(“liv_titulo”, titulo);
registo.put(“liv_autor”, autor);
return db.insert(“livro”, null, registo);
}

//—consulta todos os livros—
public Cursor getAllLivros() throws SQLException
{
return db.query(true,
“livro”,
new String[] {“_id”,”liv_titulo”,”liv_autor”},
null,
null,
null,
null,
null,
null);
}
}

Segue-se o código da Activity que gere a BD e a interface XML criada mais abaixo.

package se.bd;

import android.app.Activity;
import android.database.Cursor;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

public class BD extends Activity {
Button inserir, consultar;
EditText titulo, autor;
DBAdapter bd;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);

inserir = (Button)findViewById(R.id.inserir);
consultar = (Button)findViewById(R.id.consultar);
titulo = (EditText)findViewById(R.id.titulo);
autor = (EditText)findViewById(R.id.autor);

bd = new DBAdapter(this);
bd.open();

inserir.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
String sTitulo = titulo.getText().toString();
String sAutor = autor.getText().toString();
bd.insereLivro(sTitulo, sAutor);
titulo.setText(“”);
autor.setText(“”);
}
});

consultar.setOnClickListener(new View.OnClickListener() {
public void onClick(View v) {
mostraLivros();
}
});
}

public void mostraLivros() {
Cursor c = bd.getAllLivros();
if(c.moveToFirst()) {
do {
int id = c.getInt(0);
String sTitulo = c.getString(1);
String sAutor = c.getString(2);
String msg = String.format(“%d, %s, %s”, id, sTitulo, sAutor);
Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
}while(c.moveToNext());
}
c.close();
}

public void onDestroy() {
super.onDestroy();
bd.close();
}
}

O layout gráfico da aplicação é dados pelo ficheiro XML seguinte:

<?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” ><TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Título”
android:textSize=”20sp”
/>
<EditText
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/titulo”
android:inputType=”text”
android:textSize=”20sp”
/>
<TextView
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:text=”Autor”
android:textSize=”20sp”
/>
<EditText
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/autor”
android:inputType=”text”
android:textSize=”20sp”
/>
<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/inserir”
android:text=”Inserir”
android:textSize=”20sp”
/>
<Button
android:layout_width=”fill_parent”
android:layout_height=”wrap_content”
android:id=”@+id/consultar”
android:text=”Consultar”
android:textSize=”20sp”
/></LinearLayout>

A aplicação tem duas caixas de texto (EditText) para o utilizador colocar o título do livro e nome do autor. Tem também dois botões: um para inserir um novo livro e o outro para consultar os livros já armazenados.

Segue-se uma imagem do layout da aplicação.

device-2013-04-03-030339

Mapas e geolocalização com MapFragment

Ficheiros da aplicação

  • No menu, abrir a vista de Navigator: Window – Show View – Navigator
  • Em Navigator (janela do lado esquerdo), premir o botão direito do rato e escolher New – Project. Depois selecionar Android e Android Application Project. Premir Next. Surge uma janela de diálogo.
    • Em Application Name, colocar “Mapas”
    • Em Project Name, colocar “Mapas”
    • Em package name, colocar “com.lingweb.mapa”
    • Em Minimum Required SDK selecionar Google APIs 3.0
    • Em Target SDK selecionar Google APIs 4.2 (ou versão mais recente)
    • Premir Next três vezes
    • Em Create Activity, selecionar BlankActivity. Premir Next
    • Em Activity Name, colocar “Mapas”
    • Em Layout Name, colocar “mapas_layout”
    • Premir Finish
  • Em Navigator, premir o botão direito do rato sobre o projeto Mapas e criar uma diretoria com o nome “deploy”
  • Em Navigator, abrir o ficheiro AndroidManifest.xml e colocar dentro da marca application, as seguinte linhas (antes de <application>):

<permission
android:name=”com.example.mapdemo.permission.MAPS_RECEIVE”
android:protectionLevel=”signature” />

<uses-feature
android:glEsVersion=”0x00020000″
android:required=”true” />

<uses-permission
android:name=”com.example.mapdemo.permission.MAPS_RECEIVE” />
<uses-permission
android:name=”android.permission.INTERNET” />
<uses-permission
android:name=”android.permission.WRITE_EXTERNAL_STORAGE” />
<uses-permission android:name=
“com.google.android.providers.gsf.permission.READ_GSERVICES” />
<uses-permission
android:name=”android.permission.ACCESS_COARSE_LOCATION” />
<uses-permission
android:name=”android.permission.ACCESS_FINE_LOCATION” />

e no fim (antes de </application>):

<meta-data
android:name=”com.google.android.maps.v2.API_KEY”
android:value=”XXX…XXX” />

Na linha anterior deve ser colocada a chave fornecida pelo Google.

  • Em Navigator, abrir o ficheiro /res/layout/mapas_layout.xml e substituir o seu conteúdo pelo código seguinte. No parâmetro android:apiKey deve colocar a chave que obteve no Google. O código abaixo contém três objetos que constituem o esquema gráfico da aplicação: o primeiro é um objeto do tipo MapView, onde é colocado o mapa, os outros dois são textos onde é escrita a posição (latitude e longitude) do telemóvel.

<RelativeLayout       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=”.Mapas” >

<fragment
android:id=”@+id/map”
android:layout_width=”match_parent”
android:layout_height=”match_parent”
/>

</RelativeLayout>

É necessário importar a biblioteca do Google Play services descarregada anteriormente através do SDK Manager e incluí-la no projeto.

  • No Eclipse, selecionar:
  • File – Import – Android – Existing Android Code into Workspace
  • Premir Next. Em Browse, selecionar o caminho para a biblioteca:
    C:\adt-bundle-windows-x86_64\sdk\extras\google\google_play_services\ \libproject\google-play-services_lib
  • Marcar a opção Copy projects into workspace e premir Finish
  • Em Navigator, premir o botão direito do rato sobre o projeto e selecionar Properties
  • Selecionar Android. Em Library, não marcar a caixa Is Library e adicionar a biblioteca google-play-services_lib
  • Por fim, colocar o código da aplicação:
  • Em Navigator, dentro das diretorias do projeto, abrir o ficheiro /src/com/ /lingweb/mapa/Mapas.java e colocar o código:
package com.lingweb.mapa;import android.app.Activity;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.os.Bundle;
import android.widget.Toast;import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.model.BitmapDescriptorFactory;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.Marker;
import com.google.android.gms.maps.model.MarkerOptions;

public class Mapas extends Activity implements LocationListener {
static final LatLng LISBOA = new LatLng(38.725, -9.151);
private GoogleMap mapa;
LocationManager lm;
Marker lisboa, aqui;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.mapas_layout);
lm = (LocationManager) getSystemService(LOCATION_SERVICE);
lm.requestLocationUpdates(LocationManager.GPS_PROVIDER, 5000, 3, this);
mapa = ((MapFragment) getFragmentManager().findFragmentById(R.id.map))
.getMap();
lisboa = mapa.addMarker(new MarkerOptions().position(LISBOA)
.title(“Lisboa”));
aqui = mapa.addMarker(new MarkerOptions()
.position(LISBOA)
.title(“Bom dia”)
.snippet(“Você está aqui”)
.icon(BitmapDescriptorFactory
.fromResource(R.drawable.ic_launcher)));

// Move a câmara instanteneamente para lisboa com um zoom de 15.
mapa.moveCamera(CameraUpdateFactory.newLatLngZoom(LISBOA, 10));
if ( !lm.isProviderEnabled( LocationManager.GPS_PROVIDER ) ) {
Toast.makeText(getApplicationContext(), “Por favor, ative o GPS”, Toast.LENGTH_LONG).show();
}

}

@Override
public void onLocationChanged(Location loc) {
double lat = loc.getLatitude();
double lng = loc.getLongitude();
LatLng sitio = new LatLng(lat, lng);
// Zoom in, com animação da câmara.
mapa.animateCamera(CameraUpdateFactory.newLatLngZoom(sitio, 15), 2000, null);
aqui.setPosition(sitio);
}

@Override
public void onProviderDisabled(String a) {}
@Override
public void onProviderEnabled(String a) {}
@Override
public void onStatusChanged(String a, int b, Bundle c) {}

}

No caso do Eclipse produzir um erro devido à falta de tradução de cadeias de carateres, o problema pode ser contornado da seguinte forma:

  • No Eclipse, em Window – Preferences, selecionar Android e na caixa procurar CorrectnessMessages
  • Premir ExtraTranslation e alterar a Severity para Warning. Fazer o mesmo com a opção MissingTranslation

As aplicações que usam a biblioteca google-play-services_lib não podem ser executadas no emulador. É necessário criar uma aplicação assinada e executá-la num dispositivo físico.
Para executar esta aplicação tem que instalar o Google Play Services no dispositivo, a partir da loja do Android: Google Play.

Android Debug Bridge

Neste artigo, apresenta-se um exemplo de execução remota da aplicação OlaAndroid através do Android Debug Bridge

  • Em consola, iniciar o emulador. Para isso, descer à diretoria de ferramentas do Android
    cd ” C:adt-bundle-windows-x86_64sdk ools>”
  • e depois executar o comando
    emulator -avd avd_01
  • Mudar, depois, para a diretoria platform-tools
    cd ” C:adt-bundle-windows-x86_64sdkplatform-tools>”
  • O comando seguinte instala a aplicação Ola.apk no emulador:
    adb install Ola.apk
    (é necessário que o ficheiro Ola.apk esteja na mesma diretoria, caso contrário deve fornecer-se o caminho completo para o APK)
  • O comando seguinte mostra a lista de ficheiros da diretoria raiz no cartão de memória do emulador
    adb shell ls /

Se tiver mais do que um dispositivo Android ativo, é necessário indicar qual o dispositivo a que pretende aceder. Nesse caso, o comando acima pode ser substituído por:
adb -s emulator-5554 shell ls /

  • Para listar todos os dispositivos ativos, executar o comando:
    adb devices
  • O comando seguinte cria uma diretoria no cartão de memória do emulador (no caso de ter permissões de escrita):
    adb shell mkdir /sdcard/imagens
  • O comando seguinte copia um ficheiro do disco do computador para o emulador (no caso de ter permissões de escrita):
    adb push foto23.jpg /sdcard/imagens/f1.jpg

O comando seguinte apaga o ficheiro /sdcard/imagens/f1.jpg que se encontra no cartão de memória do emulador do Android:

adb shell rm /sdcard/imagens/f1.jpg

É possível aceder remotamente, via telnet, ao emulador para executar alguns comandos de controlo do mesmo. O primeiro emulador está disponível no porto 5554. Se houver um segundo emulador ativo, este é afeto ao porto 5555, e assim sucessivamente. Os comandos que é possível executar com acesso via telnet são os seguintes:

help|h|? print a list of commands
event simulate hardware events
geo Geo-location commands
gsm GSM related commands
kill kill the emulator instance
network manage network settings
power power related commands
quit|exit quit control session
redir manage port redirections
sms SMS related commands
avd manager virtual device state
window manage emulator window

Seguem-se alguns exemplos de utilização do acesso via telnet.

Acesso remoto ao emulador via telnet

  • Em consola, e com o emulador ativo, executar o seguinte comando:
    telnet localhost 5554
  • Depois, na sessão de telnet enviar os comandos que se seguem
    • Enviar dados de localização (GPS) para o emulador, onde a longitude é o primeiro parâmetro e a latitude o segundo:
      geo fix -9.145174 38.733660
    • Para conhecer os parâmetros do comando network
      help network
    • Para saber o estado da rede
      network status
    • Para saber o estado da bateria
      power display
    • Abandonar a sessão de telnet
      exit