web-dev-qa-db-fra.com

Exemple: Android socket réseau bidirectionnel utilisant AsyncTask

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:

Screenshot of Application

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
    }
}
37
Lance Lefebure

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;
}
5
jt-gilkeson

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é.

enter image description here

Utilisation 1:

  • obtenir Android et un bureau Linux sur un LAN
  • trouver l'IP du bureau avec ifconfig
  • courir netcat -l 12345 sur un terminal
  • sur Android, saisissez l'IP du bureau
  • cliquez sur contacter le serveur
  • sur le terminal, saisissez la réponse et appuyez sur Ctrl + D
  • il apparaît sur le output: section

Utilisation 2:

  • nom d'hôte google.com
  • port 80
  • Message: "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:

  1. Le thread d'arrière-plan configure la connexion de socket puis se met en veille (via wait ()).
  2. En appuyant sur le bouton, SendDataToNetwork ajoute des données à la file d'attente et réveille le thread d'arrière-plan (via notify()).
  3. Lorsque le thread d'arrière-plan se réveille, il vérifie d'abord l'indicateur 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.
  4. Vous devriez avoir la méthode 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

3
Peter Knego