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>
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
}
}
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;
}
よりインタラクティブな例
OPに似ていますが、ホスト、ポート、メッセージを制御できます+接続が失敗した場合、ポップアップエラー通知があります。
使用法1:
ifconfig
でデスクトップのIPを見つけますnetcat -l 12345
端末上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);
}
}
}
Androidサーバーの例: https://stackoverflow.com/a/35745834/895245
テスト済みAndroid 5.1.1、Sony Xperia 3 D6643。
SendDataToNetwork
は、doInBackground()
と同じスレッドでは実行されません。 SendDataToNetwork
は、ソケットの準備ができる前にデータの送信を開始する可能性があります。
これをすべて回避するには、SendDataToNetwork
を使用してデータを保存し、データを送信する準備ができたことをバックグラウンドスレッドに通知します。
古いデータがまだ送信されている間に、ユーザーがボタンを複数回押す可能性があるため、NetworkTask内でキューを同期する必要があります。次に:
SendDataToNetwork
がキューにデータを追加し、バックグラウンドスレッドを起動します(notify()
経由)。finish
フラグをチェックします。設定されている場合、接続を閉じて終了します。そうでない場合は、キューからデータを読み取り、ネットワークに送信して、スリープ状態に戻ります。finish
フラグ(ブール値などの原子変数)を設定し、バックグラウンドスレッドを起動するfinish()
メソッドが必要です。これは、バックグラウンドスレッドを正常に終了する方法です。スレッドの同期がどのように行われるかを見てください: http://www.jchq.net/tutorial/07_03Tut.htm