Example: Android bi-directional network socket using AsyncTask

Solution 1:

The SendDataToNetwork task runs in the main ui thread, meaning it will crash a Honeycomb or higher app due to NetworkOnMainThreadException Fatal exception. Here's what my SendDataToNetwork looks like to avoid this issue:

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

Solution 2:

Your SendDataToNetwork does not run on the same thread as doInBackground(). There is a possibility that SendDataToNetwork would start sending data before socket is ready.

To avoid all this just use SendDataToNetwork to save data and signal to background thread that data is ready to be sent.

Since there is possibility that user can press button multiple times, while the old data is still being sent, you should have synchronized Queue inside NetworkTask. Then:

  1. Background thread sets up the socket connection and then goes to sleep (via wait()).
  2. On button press, SendDataToNetwork adds data to queue and wakes up the background thread (via notify()).
  3. When background thread wakes up, it first checks the finish flag. If set, it closes connections and exits. If not it reads data from Queue, sends it to network and goes back to sleep.
  4. You should have finish() method which sets a finish flag (atomic variable, like boolean) and wakes the background thread. This is a way to gracefully exit the background thread.

Take a look at how thread synchronization is done: http://www.jchq.net/tutorial/07_03Tut.htm

Solution 3:

More interactive example

Similar to the OP's, but you can control host, port and message + there is a popup error notification if the connection failed.

enter image description here

Usage 1:

  • get Android and a Linux desktop on a LAN
  • find the IP of the desktop with ifconfig
  • run netcat -l 12345 on a terminal
  • on Android, fill in the IP of the desktop
  • click contact server
  • on the terminal, type the reply, and hit Ctrl + D
  • it appears on the output: section

Usage 2:

  • hostname google.com
  • port 80
  • Message: "GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"

Note that some HTTP servers won't close after the reply expecting further requests, and the application will hang until they timeout. Such servers expect you to parse the Content-Width header and close yourself.

If the connection fails, an alert message is shown to the user on a dialog.

Code

Add to AndroidManifest.xml:

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

And the main activity is:

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

On GitHub with build boilerplate.

I've also posted an Android server example at: https://stackoverflow.com/a/35745834/895245

Tested on Android 5.1.1, Sony Xperia 3 D6643.