Enviar dados para o servidor com Android

Em um post anterior, eu descrevi como criar uma aplicação para receber dados no servidor. Nesse post eu vou criar uma aplicação Android que envia dados para o servidor. Alguns pontos precisam ser levados em consideração:

  1. Toda conexão (vamos usar Socket nesse exemplo) precisa ser feita em uma thread separada
  2. O Android não permite alterar componentes visuais em threads separadas
  3. O servidor tem que estar ouvindo em um IP que o Android possa conectar


Antes de abordarmos o código de fato e esses pontos, vamos criar primeiro o design da aplicação. O layout principal vai consistir em um campo que você pode informar o host e a porta, um campo de texto para você escrever os dados, um botão para enviar os dados e um label para exibir o status.

activity_main.xml
activity_main.xml

Isso pode ser contemplado com o seguinte código de layout:

<?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" >
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content" >
        <EditText
            android:id="@+id/txtHostPort"
            android:layout_width="236dp"
            android:layout_height="wrap_content"
            android:layout_alignParentRight="true"
            android:layout_alignParentTop="true"
            android:layout_toRightOf="@+id/textView1"
            android:ems="10"
            android:hint="@string/host_port_hint" >
            <requestFocus />
        </EditText>
        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_alignParentLeft="true"
            android:layout_centerVertical="true"
            android:text="@string/host_port_label" />
    </RelativeLayout>
    <EditText
        android:id="@+id/txtValor"
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="0.50"
        android:ems="10"
        android:inputType="textMultiLine" />
    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="@string/btn_send" />
    <TextView
        android:id="@+id/txtStatus"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text=""
        android:textSize="20sp" />
</LinearLayout>

Também devemos dar permissão de conexão (internet) para o aplicativo. No AndroidManifest.xml adicione a seguinte permissão:

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

Agora vamos ao primeiro ponto: "Toda conexão (vamos usar Socket nesse exemplo) precisa ser feita em uma thread separada". Para realizar esse feito vamos utilizar um AsyncTask. O AsyncTask é uma classe excelente para quando precisamos realizar tarefas separadas da thread principal de maneira assíncrona e precisamos também atualizar a interface com o usuário que está em outra thread. Essa classe possui um método chamado publishProgress. Ao chamar esse método ele automaticamente chama o método onProgressUpdate que pode ser utilizado para atualizar a interface com o usuário. Dessa maneira também solucionamos o ponto 2: "O Android não permite alterar componentes visuais em threads separadas".

O processamento principal deve ser feito no método doInBackground. Com essas informações, implementei a seguinte AsyncTask:

package br.com.thiagovespa.android.socketandroid;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import android.os.AsyncTask;
import android.util.Log;
/**
 * Classe para envio de dados via socket
 *
 * @author Thiago Galbiatti Vespa
 *
 */
public abstract class SocketTask extends AsyncTask<String, String, Boolean> {
	private Socket socket;
	private InputStream is;
	private OutputStream os;
	private String host;
	private int port;
	private int timeout;
	/**
	 * Construtor com host, porta e timeout
	 *
	 * @param host
	 *            host para conexão
	 * @param port
	 *            porta para conexão
	 * @param timeout
	 *            timeout da conexão
	 */
	public SocketTask(String host, int port, int timeout) {
		super();
		this.host = host;
		this.port = port;
		this.timeout = timeout;
	}
	/**
	 * Envia dados adicionais se estiver conectado
	 *
	 * @param data
	 *            dados addicionais
	 * @throws IOException
	 */
	public void sendData(String data) throws IOException {
		if (socket != null && socket.isConnected()) {
			os.write(data.getBytes());
		}
	}
	@Override
	protected Boolean doInBackground(String... params) {
		boolean result = false;
		try {
			SocketAddress sockaddr = new InetSocketAddress(host, port);
			socket = new Socket();
			socket.connect(sockaddr, timeout); // milisegundos
			if (socket.isConnected()) {
				publishProgress("CONNECTED");
				is = socket.getInputStream();
				os = socket.getOutputStream();
				for (String p : params) {
					os.write(p.getBytes());
				}
				byte[] buff = new byte[2048];
				int buffData = is.read(buff, 0, 2048);
				while (buffData != -1) {
					String response = new String(buff);
					// Envia os dados
					publishProgress(response);
					buffData = is.read(buff, 0, 2048);
				}
			} else {
				publishProgress("CONNECT ERROR");
			}
		} catch (IOException e) {
			publishProgress("ERROR");
			Log.e("SocketAndroid", "Erro de entrada e saida", e);
			result = true;
		} catch (Exception e) {
			publishProgress("ERROR");
			Log.e("SocketAndroid", "Erro generico", e);
			result = true;
		} finally {
			try {
				if (is != null) {
					is.close();
				}
				if (os != null) {
					os.close();
				}
				if (socket != null) {
					socket.close();
				}
			} catch (Exception e) {
				Log.e("SocketAndroid", "Erro ao fechar conexao", e);
			}
		}
		return result;
	}
}

O código realiza um conexão socket (linha 62, 63 e 64), envia os dados (linha 70) e envia a resposta para a tela na linha 77. Agora é só utilizá-la na Activity principal.

package br.com.thiagovespa.android.socketandroid;
import java.text.SimpleDateFormat;
import java.util.Date;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.TextView;
public class MainActivity extends Activity {
	private Button btnSend;
	private TextView txtStatus;
	private TextView txtValor;
	private TextView txtHostPort;
	private SocketTask st;
	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		btnSend = (Button) findViewById(R.id.btnSend);
		txtStatus = (TextView) findViewById(R.id.txtStatus);
		txtValor = (TextView) findViewById(R.id.txtValor);
		txtHostPort = (TextView) findViewById(R.id.txtHostPort);
		btnSend.setOnClickListener(btnConnectListener);
	}
	private OnClickListener btnConnectListener = new OnClickListener() {
		public void onClick(View v) {
			// Recupera host e porta
			String hostPort = txtHostPort.getText().toString();
			int idxHost = hostPort.indexOf(":");
			final String host = hostPort.substring(0, idxHost);
			final String port = hostPort.substring(idxHost + 1);
			// Instancia a classe de conexão com socket
			st = new SocketTask(host, Integer.parseInt(port), 5000) {
				@Override
				protected void onProgressUpdate(String... progress) {
					SimpleDateFormat sdf = new SimpleDateFormat(
							"dd/MM/yyyy HH:mm:ss");
					// Recupera o retorno
					txtStatus.setText(sdf.format(new Date()) + " - "
							+ progress[0]);
				}
			};
			st.execute(txtValor.getText() == null ? "" : txtValor.getText()
					.toString()); // Envia os dado
		}
	};
	@Override
	protected void onDestroy() {
		super.onDestroy();
		st.cancel(true);
	}
}

O primeiro passo é instanciar a SocketTask (linha 41) sobreescrever o método onProgressUpdate (linha 43) e invocar o método execute (linha 52), que é responsável por colocar a thread nova em execução.

Após isso, precisamos resolver o ponto 3: "O servidor tem que estar ouvindo em um IP que o Android possa conectar". Inicie o aplicativo desse post utilizando um IP que esteja na mesma rede do Android ao invés de 127.0.0.1. Execute a aplicação Android, informe o IP e a porta utilizados para iniciar o servidor, escreva um texto e clique no botão "Enviar Dados". O servidor irá receber os dados e gravar em um arquivo.

O código completo com o projeto para o eclipse está disponível aqui. Utilizando o mesmo raciocínio dá para fazer várias aplicações, como obter dados do servidor, realizar um chat, gravar imagens no servidor, ... É só usar a criatividade.

Sobre: Thiago Galbiatti Vespa

Thiago Galbiatti Vespa é mestre em Ciências da Computação e Matemática Computacional pela USP e bacharel em Ciências da Computação pela UNESP. Coordenador de projetos do JavaNoroeste, membro do JCP (Java Community Process), consultor Oracle, arquiteto de software de empresas de médio e grande porte, palestrante de vários eventos e colaborador de projetos open source. Possui as certificações: Oracle Certified Master, Java EE 5 Enterprise Architect – Step 1, 2 and 3; Oracle WebCenter Portal 11g Certified Implementation Specialist; Oracle Service Oriented Architecture Infrastructure Implementation Certified Expert; Oracle Certified Professional, Java EE 5 Web Services Developer; Oracle Certified Expert, NetBeans Integrated Development Environment 6.1 Programmer; Oracle Certified Professional, Java Programmer; Oracle Certified Associate, Java SE 5/SE 6