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:
- Toda conexão (vamos usar Socket nesse exemplo) precisa ser feita em uma thread separada
- O Android não permite alterar componentes visuais em threads separadas
- 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.
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.
Boa tarde Thiago, preciso de um aplicativo que envie fotos e digitalização do aparelho android direto para meu servidor, resumindo muito é isso que preciso, você tem disponibilidade para desenvolve-lo? Caso sim favor me passar um orçamento.
Olá Thiago.
Parabéns pela didática aplicada e pela clareza no código.
Estou testando este post e encontrei o seguinte problema durante a
execução de envio:
Cenário: aplicativo socket utils na porta 8090 funcionando ok
Ao clicar no botão ocorre o seguinte erro registrado em log:
07-18 20:14:01.236: I/Choreographer(844): Skipped 50 frames! The application may be doing too much work on its main thread.
07-18 20:14:04.540: I/Choreographer(844): Skipped 44 frames! The application may be doing too much work on its main thread.
07-18 20:14:05.497: D/AndroidRuntime(844): Shutting down VM
07-18 20:14:05.497: W/dalvikvm(844): threadid=1: thread exiting with uncaught exception (group=0x40a71930)
07-18 20:14:05.547: E/AndroidRuntime(844): FATAL EXCEPTION: main
07-18 20:14:05.547: E/AndroidRuntime(844): java.lang.StringIndexOutOfBoundsException: length=0; regionStart=0; regionLength=-1
07-18 20:14:05.547: E/AndroidRuntime(844): at java.lang.String.startEndAndLength(String.java:583)
07-18 20:14:05.547: E/AndroidRuntime(844): at java.lang.String.substring(String.java:1464)
07-18 20:14:05.547: E/AndroidRuntime(844): at br.com.thiagovespa.android.socketandroid.MainActivity$1.onClick(MainActivity.java:37)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.view.View.performClick(View.java:4204)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.view.View$PerformClick.run(View.java:17355)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.os.Handler.handleCallback(Handler.java:725)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.os.Handler.dispatchMessage(Handler.java:92)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.os.Looper.loop(Looper.java:137)
07-18 20:14:05.547: E/AndroidRuntime(844): at android.app.ActivityThread.main(ActivityThread.java:5041)
07-18 20:14:05.547: E/AndroidRuntime(844): at java.lang.reflect.Method.invokeNative(Native Method)
07-18 20:14:05.547: E/AndroidRuntime(844): at java.lang.reflect.Method.invoke(Method.java:511)
07-18 20:14:05.547: E/AndroidRuntime(844): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:793)
07-18 20:14:05.547: E/AndroidRuntime(844): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:560)
07-18 20:14:05.547: E/AndroidRuntime(844): at dalvik.system.NativeStart.main(Native Method)
Se possível responda pelo e-mail, grato.
O que tem nessa linha de código: MainActivity.java:37 ?
Possivelmente você está usando o método substring de forma errada. Com um string vazia e tentando acessar uma posição que não existe, por isso o StringIndexOutOfBoundsException.
Olá Thiago, eu gostaria de algo que fizesse o contrário, ou seja, que um servidor enviasse uma mensagem para um android. O que pretendo fazer é um trabalho de inclusão, onde pessoas surdas possam receber uma mensagem tipo ("Tocou o alarme IFF") e acionar o vibra call quando soar o alarme no Instituto onde estudam, lógico que pra isso o relógio do servidor deverá estar sincronizado com o relógio que toca o alarme, (isso é fácil), o detalhe está em fazer com que somente alguns aparelhos recebessem essa mensagem, ou os telefones cadastrados, ou os telefones q tiveram uma app especifica, e isso poderia ser enviado via uma rede wireless/wifi, pois temos várias redes internas no Instituto.
Se puder me enviar algo ficarei grato.
Boa Madrugada.... fiz o envio de mensagem com PUSH GCM, mais no meu caso que demora muito o recebimento no celular se eu colocar no wirelles ficar rapido o recebimento, tenho net claro que funciona bem quando faco alguma requisicao, vc acha melhor fazer via socket ainda nao usei, preciso ter velocidade na comunicacao com servidor com celular android; voce pode ajudar-me.
Socket é uma boa solução.
Cara... Vc poderia me ajudar? Meu problema é o seguinte:
Tenho uma aplicação servidor, em java. E uma aplicação cliente, em android. Quando eu utilizo o emulador do eclipse, ele funciona normal... Porem, quando instalo o app no celular(Ja testei 2 celulares), ela não conecta de forma alguma, dando erro!
Obs: Acredito q seja alguma proteção do aparelho a respeito da comunicação.
Tem como enviar o código?
Oi Thiago,
tive o mesmo problema, no emulador funciona e no celular não... É o mesmo código que você passou..
obrigada!
Consegue pegar o erro no logcat com o celular?
Ficou muito bom o tutorial!
Mas preciso colocar alem do host e da porta o restante do endereço (Ex. 192.168.0.09:8080/pasta/pasta02). Tem como fazer isso??
Agradeço desde já.
Tem sim.
Oi Thiago, preciso de uma ajuda, tentei rodar os dois programas aqui citados, o aplicativo Android e o servidor, porém quando mando enviar os dados pelo aplicativo ele aparece a data e "ERROR", referente a essa parte do código:
catch (IOException e) {
publishProgress("ERROR");
Log.e("SocketAndroid", "Erro de entrada e saida", e);
result = true;
}
o que eu posso fazer?
Obrigada pelas postagens e pela ajuda!
Você consegue me passar o erro que dá no LogCat?
Olá, em primeiro lugar parabéns pelo post. No meu caso eu só precisava dar um "GET + url" não existe uma maneira mais simples de fazer isso? No caso, estou tentando fazer o android acionar o Arduino + Ethernet Shield, sé precisava que o android desse um comando GET na url 192.168.1.55/L por exemplo, não preciso de nenhum retorno, nem baixar nenhuma informação da página, nada. Tem jeito? Abraço!
Oi Alvaro!
Conseguiu fazer alguma coisa como o arduino trabalhando desta forma?
Quero fazer mais ou menos isto que você está tentando fazer.
Consegui sim... tanto arduino, quanto raspberry pi (que fica mais fácil, pois dá pra subir um servidor nele)
então estou trabalhando com o novo Android Studio e fiz estou programa que está aqui no blog para funcionar ele mas o que consegui foi apenas saber que o arduino está conectado ou não.
Eu escrevo um caracter no celular e envio mas não acontece nada no meu arduino pode me dar uma dica.
Pois fiz a mesma idéia do programa utilizando o App Inventor e funcionou mas com este exemplo que tem no site não.
Pode me ajudar?
Teria como você me enviar seu código?
Segue o código que utilizei no arduino para ser feito um exemplo de conexão
/*
Web Server
A simple web server that shows the value of the analog input pins.
using an Arduino Wiznet Ethernet shield.
Circuit:
* Ethernet shield attached to pins 10, 11, 12, 13
* Analog inputs attached to pins A0 through A5 (optional)
*/
#include
#include
// Enter a MAC address and IP address for your controller below.
// The IP address will be dependent on your local network:
byte mac[] = {
0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED
};
IPAddress ip(192, 168, 0, 177);
EthernetServer server(80);
const byte ledPin = A0;// Select pin for Main Light
String readString;
void setup() {
// Open serial communications and wait for port to open:
pinMode(ledPin, OUTPUT);
Serial.begin(9600);
while (!Serial) {
; // wait for serial port to connect. Needed for Leonardo only
}
// start the Ethernet connection and the server:
Ethernet.begin(mac, ip);
server.begin();
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
}
void loop() {
// listen for incoming clients
EthernetClient client = server.available();
if (client) {
Serial.println("new client");
// an http request ends with a blank line
boolean currentLineIsBlank = true;
while (client.connected()) {
if (client.available()) {
char c = client.read();
if (readString.length() < 100)
{
readString = readString + c;// Store characters to string
}
Serial.write(c);
// if you've gotten to the end of the line (received a newline
// character) and the line is blank, the http request has ended,
// so you can send a reply
if (c == '\n' && currentLineIsBlank) {
// send a standard http response header
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Connection: close"); // the connection will be closed after completion of the response
client.println("Refresh: 5"); // refresh the page automatically every 5 sec
client.println();
client.println("");
client.println("");
client.println(F("Home Web Control"));
client.println(F("Teste de Conexão Arduino UNO R3 Ethernet Shield Web Server"));
client.println(F("WebServer teste de conexão"));
client.print(F(""));
client.println(F(""));
//client.println(F(""));
client.println(F(""));
// output the value of each analog input pin
for (int analogChannel = 0; analogChannel < 6; analogChannel++) {
int sensorReading = analogRead(analogChannel);
client.print("analog input ");
client.print(analogChannel);
client.print(" is ");
client.print(sensorReading);
client.println("");
}
if(readString.indexOf("/?led_on") > 0) digitalWrite(ledPin, HIGH);
if(readString.indexOf("/?led_off") > 0) digitalWrite(ledPin, LOW);
readString = "";// Clearing string for next read
client.println("");
break;
}
if (c == '\n') {
// you're starting a new line
currentLineIsBlank = true;
}
else if (c != '\r') {
// you've gotten a character on the current line
currentLineIsBlank = false;
}
}
}
// give the web browser time to receive the data
delay(1);
// close the connection:
client.stop();
Serial.println("client disconnected");
}
}
Você consegue me dizer se no log do Serial aparece a conexão:
Serial.print("server is at ");
Serial.println(Ethernet.localIP());
??
sim consigo!
server is at 192.168.0.177
new client
É isso que ele responde
client disconnected
new client
?led_offclient disconnected
new client
casa client disconnected
new client
teste de conexao client disconnected
new client
?led_onclient disconnected
Detalhe o aplicativo no android funciona apenas uma vez para que o arduino receba um outro comando tenho que fechar o aplicativo e abri-lo novamente.
------------------------------
Mas eu tenho outro aplicativo que foi feito no AppInventor que está funcionando com esse codigo do arduino e ele responde os seguintes informações.
server is at 192.168.0.177
new client
GET /?led_on HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.2; LG-D295 Build/KOT49I)
Host: 192.168.0.177
Connection: Keep-Alive
Accept-Encoding: gzip
client disconnected
new client
GET /?led_off HTTP/1.1
User-Agent: Dalvik/1.6.0 (Linux; U; Android 4.4.2; LG-D295 Build/KOT49I)
Host: 192.168.0.177
Connection: Keep-Alive
Accept-Encoding: gzip
Thiago, bom dia!
Muito bom seu post, parabéns! Foi de grande ajuda.
Estou com algumas dúvidas e gostaria de saber se você pode saná-las:
- Depois de enviar os dados para o servidor a conexão fica aberta rodando em background, como faço para fechar a conexão com o servidor após o envio dos dados?
- Se eu tento enviar mais dados usando a mesma função (st.execute) dá erro falando que a task já está em uso, como proceder?
Desde já agradeço.
Bom dia Thiago,
acompanho alguns de seus tutoriais, são muitos bons e realmente consigo aprender. Nesse exemplo de socket, ao clicar em "enviar dados" o app para de responder, será que devo adicionar mais algumas permissões no manifest alem de
?o servidor esta ok, o webservice acesso tambem em minha rede, executei a aplicação para criar a conexão socket e ele esta aguardando conexões.
Estou usando um ambiente em 4.1.2, poderia me informar se preciso adicionar algo para que a aplicação instancie a classe de conexão com o socket?
Desde já grato !
Dá algum erro? Você está fazendo a operação em thread separada?
Olá thiago! Parabéns pelo excelente blog.
Mano para enviar é entendi mas no caso de testar a conexão antes de enviar os dados? Nossa rede de internet/3g é peśsima portanto queria enviar os dados só depois de verificar se a conexão esta ok, como é possível? Agradeço a atenção.
fiz tudo certinho mas quando clico no botao para enviar os dados o app da erro . por que?
post muito bom valeu ai
Qual erro?
A aplicação fecha
Funcionou porém depois de enviar uma mensagem não é possível enviar outra sem fechar e abrir o cliente novamente. Como resolver?