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:
- Background thread sets up the socket connection and then goes to sleep (via wait()).
- On button press,
SendDataToNetwork
adds data to queue and wakes up the background thread (vianotify()
). - 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. - You should have
finish()
method which sets afinish
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.
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.