敢说敢作敢为, 无怨无恨无悔。这篇文章主要讲述示例:使用AsyncTask的Android双向网络套接字相关的知识,希望能为你提供帮助。
我在android上找到的大多数网络套接字示例都是单向的。我需要一个双向数据流的解决方案。我最终了解到了AsyncTask。此示例显示如何从套接字获取数据并将数据发送回它。由于正在接收数据的套接字的阻塞性质,阻塞需要在UI线程以外的线程中运行。
为了举例,此代码连接到Web服务器。按“Start AsyncTask”按钮将打开套接字。套接字打开后,Web服务器将等待请求。按“发送消息”按钮将向服务器发送请求。服务器的任何响应都将显示在TextView中。对于http,一旦发送了所有数据,Web服务器将断开与客户端的连接。对于其他TCP数据流,连接将一直保持到一侧断开连接。
截图:
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>
【示例(使用AsyncTask的Android双向网络套接字)】水库布局 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>
SRCcom.exampleasynctaskMainActivity.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");
}
};
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 = https://www.songbingjia.com/android/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
}
}
答案
SendDataToNetwork
任务在主ui线程中运行,这意味着由于NetworkOnMainThreadException
致命异常,它将导致Honeycomb或更高版本的应用程序崩溃。这是我的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;
}
另一答案你的
SendDataToNetwork
不会和doInBackground()
在同一个线程上运行。 SendDataToNetwork
有可能在套接字准备好之前开始发送数据。为了避免这一切,只需使用
SendDataToNetwork
将数据和信号保存到后台线程,即可以发送数据。由于用户可能会多次按下按钮,而旧数据仍在发送,因此您应该在NetworkTask中同步Queue。然后:
- 后台线程设置套接字连接,然后进入休眠状态(通过wait())。
- 按下按钮,
SendDataToNetwork
将数据添加到队列并唤醒后台线程(通过notify()
)。 - 当后台线程唤醒时,它首先检查
finish
标志。如果设置,它将关闭连接并退出。如果不是,它从队列中读取数据,将其发送到网络并返回休眠状态。 - 你应该有
finish()
方法,它设置一个finish
标志(原子变量,像布尔值)并唤醒后台线程。这是一种优雅地退出后台线程的方法。
另一答案更多互动的例子
与OP类似,但您可以控制主机,端口和消息+如果连接失败,则会出现弹出错误通知。
文章图片
用法1:
- 在局域网上获取Android和Linux桌面
- 使用
ifconfig
找到桌面的IP - 在终端上运行
netcat -l 12345
- 在Android上,填写桌面的IP
- 单击联系服务器
- 在终端上,输入回复,然后点击
Ctrl + D
- 它出现在
output:
部分
- 主机名
google.com
- 港口
80
- 消息:
"GET / HTTP/1.1Host: google.com"
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.1Host: %s", 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。
我还发布了一个Android服务器示例:https://stackoverflow.com/a/35745834/895245
在Android 5.1.1,Sony Xperia 3 D6643上测试。
推荐阅读
- Android Socket,AsyncTask,Handler内存泄漏
- 如何在没有AppEngine for Google云端存储的情况下获取服务网址()
- 带有Gcloud错误的Rails((gcloud.preview.app.deploy)错误响应:[13]意外错误。部署版本:)
- 仅包含日期和月份的Android日期选择器
- 未捕获错误(模块“AppModule”导入的意外指令“MatFormField”。请添加@NgModule注释)
- 如何获得MONTHS,DAYS和周(android datepicker)
- 我需要在android.widget.datepicker中自定义月份名称
- 无法找到Android Studio DatePicker类[重复]
- 如何解决谷歌的Blockly未来的程序员游戏:迷宫级别