Пример: двунаправленный сетевой разъем Android с использованием AsyncTask
Большинство примеров сетевых сокетов, которые я нашел для Android, были только одно направленными. Мне понадобилось решение для двунаправленного потока данных. В конце концов я узнал об AsyncTask. В этом примере показано, как получить данные из сокета и отправить данные обратно на него. Из-за блокирующего сокета, который принимает данные, эта блокировка должна выполняться в потоке, отличном от потока пользовательского интерфейса.
Для примера, этот код подключается к веб-серверу. Нажатие кнопки "Старт AsyncTask" откроет сокет. Когда сокет открыт, веб-сервер ждет запроса. Нажатие кнопки "Отправить сообщение" отправит запрос на сервер. Любой ответ с сервера будет отображаться в TextView. В случае http, веб-сервер отключится от клиента, как только все данные будут отправлены. Для других потоков данных TCP соединение будет оставаться включенным до тех пор, пока одна сторона не отключится.
Скриншот:
![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>
Рез\расположение\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>
ЦСИ\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
}
}
Ответы
Ответ 1
Ваш SendDataToNetwork
не работает в том же потоке, что и doInBackground()
. Существует вероятность того, что SendDataToNetwork
начнет отправлять данные до готовности сокета.
Чтобы избежать всего этого, просто используйте SendDataToNetwork
, чтобы сохранить данные и подать сигнал в фоновый поток, чтобы данные были готовы к отправке.
Поскольку существует вероятность того, что пользователь может нажать кнопку несколько раз, а старые данные все еще отправляются, вы должны иметь синхронизированную очередь внутри NetworkTask. Тогда:
- Фоновый поток устанавливает соединение сокета, а затем переходит в режим сна (через wait()).
- При нажатии на кнопку
SendDataToNetwork
добавляет данные в очередь и просыпает фоновый поток (через notify()
).
- Когда фоновый поток просыпается, он сначала проверяет флаг
finish
. Если установлено, он закрывает соединения и выходы. Если он не считывает данные из очереди, отправляет их в сеть и снова возвращается в режим сна.
- У вас должен быть метод
finish()
, который устанавливает флаг finish
(атомная переменная, например boolean) и просыпает фоновый поток. Это способ изящного выхода из фонового потока.
Посмотрите, как выполняется синхронизация потоков: http://www.jchq.net/tutorial/07_03Tut.htm
Ответ 2
Задача SendDataToNetwork
выполняется в главном потоке ui, то есть это приведет к сбою приложения Honeycomb или выше из-за NetworkOnMainThreadException
Неустранимого исключения. Вот как выглядит мой SendDataToNetwork
, чтобы избежать этой проблемы:
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;
}
Ответ 3
Более интерактивный пример
Подобно OP, но вы можете управлять хостом, портом и сообщением + появляется сообщение об ошибке всплывающего окна, если соединение не удалось.
![введите описание изображения здесь]()
Использование 1:
- получите Android и рабочий стол Linux в локальной сети.
- найдите IP-адрес рабочего стола с помощью
ifconfig
- запустите
netcat -l 12345
на терминале
- на Android, заполните IP-адрес рабочего стола
- нажмите сервер контактов
- на терминале, введите ответ и нажмите
Ctrl + D
- он отображается в разделе
output:
Использование 2:
- имя хоста
google.com
- порт
80
- Сообщение:
"GET / HTTP/1.1\r\nHost: google.com\r\n\r\n"
Обратите внимание, что некоторые HTTP-серверы не закрываются после ответа, ожидающего дальнейших запросов, и приложение будет зависать до тех пор, пока не истечет время ожидания. Такие серверы ожидают, что вы проанализируете заголовок Content-Width
и закройте себя.
Если соединение не удалось, в диалоговом окне пользователю отображается предупреждающее сообщение.
код
Добавить в AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
И основной вид деятельности:
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);
}
}
}
В GitHub с шаблоном сборки.
Я также разместил пример сервера Android: fooobar.com/info/154156/...
Протестировано на Android 5.1.1, Sony Xperia 3 D6643.