La plupart des exemples de socket réseau que j'ai trouvés pour Android étaient uniquement directionnels. J'avais besoin d'une solution pour un flux de données bidirectionnel. J'ai finalement appris la AsyncTask. Cet exemple montre comment obtenir des données à partir d'un socket et lui renvoyer des données. En raison de la nature bloquante d'un socket qui reçoit des données, ce blocage doit s'exécuter dans un thread autre que le thread d'interface utilisateur.
Par exemple, ce code se connecte à un serveur Web. Appuyez sur le bouton "Démarrer AsyncTask" pour ouvrir le socket. Une fois le socket ouvert, le serveur Web attend une demande. Appuyez sur le bouton "Envoyer un message" pour envoyer une demande au serveur. Toute réponse du serveur sera affichée dans TextView. Dans le cas de http, un serveur Web se déconnectera du client une fois que toutes les données auront été envoyées. Pour les autres flux de données TCP, la connexion restera active jusqu'à ce qu'un côté se déconnecte.
Capture d'écran:
AndroidManifest.xml:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
package="com.exampleasynctask"
Android:versionCode="1"
Android:versionName="1.0">
<uses-sdk Android:minSdkVersion="8" />
<uses-permission Android:name="Android.permission.INTERNET" />
<application Android:icon="@drawable/icon" Android:label="@string/app_name">
<activity Android:name=".MainActivity"
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>
res\layout\main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:Android="http://schemas.Android.com/apk/res/Android"
Android:orientation="vertical"
Android:layout_width="fill_parent"
Android:layout_height="fill_parent"
>
<Button Android:id="@+id/btnStart" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="Start AsyncTask"></Button>
<Button Android:id="@+id/btnSend" Android:layout_width="wrap_content" Android:layout_height="wrap_content" Android:text="Send Message"></Button>
<TextView Android:id="@+id/textStatus" Android:textSize="24sp" Android:layout_width="fill_parent" Android:layout_height="wrap_content" Android:text="Status Goes Here" />
</LinearLayout>
src\com.exampleasynctask\MainActivity.Java:
package com.exampleasynctask;
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.app.Activity;
import Android.os.AsyncTask;
import Android.os.Bundle;
import Android.util.Log;
import Android.view.View;
import Android.view.View.OnClickListener;
import Android.widget.Button;
import Android.widget.TextView;
public class MainActivity extends Activity {
Button btnStart, btnSend;
TextView textStatus;
NetworkTask networktask;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
btnStart = (Button)findViewById(R.id.btnStart);
btnSend = (Button)findViewById(R.id.btnSend);
textStatus = (TextView)findViewById(R.id.textStatus);
btnStart.setOnClickListener(btnStartListener);
btnSend.setOnClickListener(btnSendListener);
networktask = new NetworkTask(); //Create initial instance so SendDataToNetwork doesn't throw an error.
}
private OnClickListener btnStartListener = new OnClickListener() {
public void onClick(View v){
btnStart.setVisibility(View.INVISIBLE);
networktask = new NetworkTask(); //New instance of NetworkTask
networktask.execute();
}
};
private OnClickListener btnSendListener = new OnClickListener() {
public void onClick(View v){
textStatus.setText("Sending Message to AsyncTask.");
networktask.SendDataToNetwork("GET / HTTP/1.1\r\n\r\n");
}
};
public class NetworkTask extends AsyncTask<Void, byte[], Boolean> {
Socket nsocket; //Network Socket
InputStream nis; //Network Input Stream
OutputStream nos; //Network Output Stream
@Override
protected void onPreExecute() {
Log.i("AsyncTask", "onPreExecute");
}
@Override
protected Boolean doInBackground(Void... params) { //This runs on a different thread
boolean result = false;
try {
Log.i("AsyncTask", "doInBackground: Creating socket");
SocketAddress sockaddr = new InetSocketAddress("192.168.1.1", 80);
nsocket = new Socket();
nsocket.connect(sockaddr, 5000); //10 second connection timeout
if (nsocket.isConnected()) {
nis = nsocket.getInputStream();
nos = nsocket.getOutputStream();
Log.i("AsyncTask", "doInBackground: Socket created, streams assigned");
Log.i("AsyncTask", "doInBackground: Waiting for inital data...");
byte[] buffer = new byte[4096];
int read = nis.read(buffer, 0, 4096); //This is blocking
while(read != -1){
byte[] tempdata = new byte[read];
System.arraycopy(buffer, 0, tempdata, 0, read);
publishProgress(tempdata);
Log.i("AsyncTask", "doInBackground: Got some data");
read = nis.read(buffer, 0, 4096); //This is blocking
}
}
} catch (IOException e) {
e.printStackTrace();
Log.i("AsyncTask", "doInBackground: IOException");
result = true;
} catch (Exception e) {
e.printStackTrace();
Log.i("AsyncTask", "doInBackground: Exception");
result = true;
} finally {
try {
nis.close();
nos.close();
nsocket.close();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
Log.i("AsyncTask", "doInBackground: Finished");
}
return result;
}
public void SendDataToNetwork(String cmd) { //You run this from the main thread.
try {
if (nsocket.isConnected()) {
Log.i("AsyncTask", "SendDataToNetwork: Writing received message to socket");
nos.write(cmd.getBytes());
} else {
Log.i("AsyncTask", "SendDataToNetwork: Cannot send message. Socket is closed");
}
} catch (Exception e) {
Log.i("AsyncTask", "SendDataToNetwork: Message send failed. Caught an exception");
}
}
@Override
protected void onProgressUpdate(byte[]... values) {
if (values.length > 0) {
Log.i("AsyncTask", "onProgressUpdate: " + values[0].length + " bytes received.");
textStatus.setText(new String(values[0]));
}
}
@Override
protected void onCancelled() {
Log.i("AsyncTask", "Cancelled.");
btnStart.setVisibility(View.VISIBLE);
}
@Override
protected void onPostExecute(Boolean result) {
if (result) {
Log.i("AsyncTask", "onPostExecute: Completed with an Error.");
textStatus.setText("There was a connection error.");
} else {
Log.i("AsyncTask", "onPostExecute: Completed.");
}
btnStart.setVisibility(View.VISIBLE);
}
}
@Override
protected void onDestroy() {
super.onDestroy();
networktask.cancel(true); //In case the task is currently running
}
}
La tâche SendDataToNetwork
s'exécute dans le thread d'interface utilisateur principal, ce qui signifie qu'elle plantera une application Honeycomb ou supérieure en raison de NetworkOnMainThreadException
exception fatale. Voici à quoi ressemble mon SendDataToNetwork
pour éviter ce problème:
public boolean sendDataToNetwork(final byte[] cmd) {
if (_nsocket.isConnected()) {
Log.i(TAG, "SendDataToNetwork: Writing received message to socket");
new Thread(new Runnable() {
public void run() {
try {
_nos.write(cmd);
} catch (Exception e) {
e.printStackTrace();
Log.i(TAG, "SendDataToNetwork: Message send failed. Caught an exception");
}
}
}).start();
return true;
}
Log.i(TAG, "SendDataToNetwork: Cannot send message. Socket is closed");
return false;
}
Exemple plus interactif
Similaire aux OP, mais vous pouvez contrôler l'hôte, le port et le message + il y a une notification d'erreur contextuelle si la connexion a échoué.
Utilisation 1:
ifconfig
netcat -l 12345
sur un terminalCtrl + D
output:
sectionUtilisation 2:
google.com
80
"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"
Notez que certains serveurs HTTP ne se fermeront pas après la réponse en attendant d'autres demandes, et l'application se bloquera jusqu'à ce qu'ils expirent. Ces serveurs s'attendent à ce que vous analysiez le Content-Width
en-tête et fermez-vous.
Si la connexion échoue, un message d'alerte s'affiche à l'utilisateur dans une boîte de dialogue.
Code
Ajouter à AndroidManifest.xml
:
<uses-permission Android:name="Android.permission.INTERNET" />
Et l'activité principale est:
import Android.app.Activity;
import Android.app.AlertDialog;
import Android.app.IntentService;
import Android.content.DialogInterface;
import Android.content.Intent;
import Android.os.AsyncTask;
import Android.os.Bundle;
import Android.util.Log;
import Android.view.View;
import Android.widget.Button;
import Android.widget.EditText;
import Android.widget.LinearLayout;
import Android.widget.ScrollView;
import Android.widget.TextView;
import Java.io.IOException;
import Java.io.InputStream;
import Java.io.OutputStream;
import Java.net.Socket;
public class Main extends Activity {
final static String TAG = "AndroidCheatSocket";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final LinearLayout linearLayout = new LinearLayout(this);
linearLayout.setOrientation(LinearLayout.VERTICAL);
TextView textView;
final String defaultHostname = "192.168.0.";
textView = new TextView(this);
textView.setText("hostname / IP:");
linearLayout.addView(textView);
final EditText hostnameEditText = new EditText(this);
hostnameEditText.setText(defaultHostname);
hostnameEditText.setSingleLine(true);
linearLayout.addView(hostnameEditText);
textView = new TextView(this);
textView.setText("port:");
linearLayout.addView(textView);
final EditText portEditText = new EditText(this);
portEditText.setText("12345");
portEditText.setSingleLine(true);
linearLayout.addView(portEditText);
textView = new TextView(this);
textView.setText("data to send:");
linearLayout.addView(textView);
final EditText dataEditText = new EditText(this);
dataEditText.setText(String.format("GET / HTTP/1.1\r\nHost: %s\r\n\r\n", defaultHostname));
linearLayout.addView(dataEditText);
final TextView replyTextView = new TextView(this);
final ScrollView replyTextScrollView = new ScrollView(this);
replyTextScrollView.addView(replyTextView);
final Button button = new Button(this);
button.setText("contact server");
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
button.setEnabled(false);
new MyAsyncTask(Main.this, replyTextView, button).execute(
hostnameEditText.getText().toString(),
portEditText.getText().toString(),
dataEditText.getText().toString());
}
});
linearLayout.addView(button);
textView = new TextView(this);
textView.setText("output:");
linearLayout.addView(textView);
linearLayout.addView(replyTextScrollView);
this.setContentView(linearLayout);
}
private class MyAsyncTask extends AsyncTask<String, Void, String> {
Activity activity;
Button button;
TextView textView;
IOException ioException;
MyAsyncTask(Activity activity, TextView textView, Button button) {
super();
this.activity = activity;
this.textView = textView;
this.button = button;
this.ioException = null;
}
@Override
protected String doInBackground(String... params) {
StringBuilder sb = new StringBuilder();
try {
Socket socket = new Socket(
params[0],
Integer.parseInt(params[1]));
OutputStream out = socket.getOutputStream();
out.write(params[2].getBytes());
InputStream in = socket.getInputStream();
byte buf[] = new byte[1024];
int nbytes;
while ((nbytes = in.read(buf)) != -1) {
sb.append(new String(buf, 0, nbytes));
}
socket.close();
} catch(IOException e) {
this.ioException = e;
return "error";
}
return sb.toString();
}
@Override
protected void onPostExecute(String result) {
if (this.ioException != null) {
new AlertDialog.Builder(this.activity)
.setTitle("An error occurrsed")
.setMessage(this.ioException.toString())
.setIcon(Android.R.drawable.ic_dialog_alert)
.show();
} else {
this.textView.setText(result);
}
this.button.setEnabled(true);
}
}
}
Sur GitHub avec build passe-partout .
J'ai également publié un Android sur: https://stackoverflow.com/a/35745834/895245
Testé sur Android 5.1.1, Sony Xperia 3 D6643.
Votre SendDataToNetwork
ne s'exécute pas sur le même thread que doInBackground()
. Il est possible que SendDataToNetwork
commence à envoyer des données avant que le socket ne soit prêt.
Pour éviter tout cela, utilisez simplement SendDataToNetwork
pour enregistrer les données et signaler au thread d'arrière-plan que les données sont prêtes à être envoyées.
Puisqu'il est possible que l'utilisateur puisse appuyer plusieurs fois sur le bouton, alors que les anciennes données sont toujours envoyées, vous devriez avoir synchronisé la file d'attente dans NetworkTask. Ensuite:
SendDataToNetwork
ajoute des données à la file d'attente et réveille le thread d'arrière-plan (via notify()
).finish
. S'il est défini, il ferme les connexions et quitte. Sinon, il lit les données de la file d'attente, les envoie au réseau et se remet en veille.finish()
qui définit un indicateur finish
(variable atomique, comme booléenne) et réveille le thread d'arrière-plan. C'est un moyen de quitter le fil d'arrière-plan avec élégance.Jetez un œil à la synchronisation des threads: http://www.jchq.net/tutorial/07_03Tut.htm