android-asynctask asynctask用法 - 示例:使用AsyncTask的Android雙向網絡套接字




android教學 thread (4)

我在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>

水庫\佈局\ 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
    }
}

Answers

更多互動的例子

與OP類似,但您可以控制主機,端口和消息+如果連接失敗,則會出現彈出錯誤通知。

用法1:

  • 在局域網上獲取Android和Linux桌面
  • 使用ifconfig查找桌面的IP
  • 在終端上運行netcat -l 12345
  • 在Android上,填寫桌面的IP
  • 單擊聯繫服務器
  • 在終端上,鍵入回复,然後Ctrl + D
  • 它出現在output:部分

用法2:

  • hostname 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服務器示例: https://stackoverflow.com/a/35745834/895245 : https://stackoverflow.com/a/35745834/895245

在Android 5.1.1,Sony Xperia 3 D6643上測試。


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。 然後:

  1. 後台線程設置套接字連接,然後進入休眠狀態(通過wait())。
  2. 按下按鈕, SendDataToNetwork將數據添加到隊列並喚醒後台線程(通過notify() )。
  3. 當後台線程喚醒時,它首先檢查finish標誌。 如果設置,它將關閉連接並退出。 如果沒有,它會從隊列中讀取數據,將其發送到網絡並重新進入休眠狀態。
  4. 你應該有finish()方法設置一個finish標誌(原子變量,如boolean)並喚醒後台線程。 這是一種優雅退出後台線程的方法。

看看如何完成線程同步: http://www.jchq.net/tutorial/07_03Tut.htmhttp://www.jchq.net/tutorial/07_03Tut.htm


我正在使用自定義鍵盤輸入​​十六進制數字,因此我無法顯示IMM鍵盤...

在v3.2.4_r1中添加了setSoftInputShownOnFocus(boolean show)以控制天氣或不顯示TextView獲得焦點時的鍵盤,但它仍然隱藏,因此必須使用反射:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR2) {
    try {
        Method method = TextView.class.getMethod("setSoftInputShownOnFocus", boolean.class);
        method.invoke(mEditText, false);
    } catch (Exception e) {
        // Fallback to the second method
    }
}

對於舊版本,我使用OnGlobalLayoutListener獲得了非常好的結果(但遠非完美),在我的根視圖中藉助OnGlobalLayoutListener添加,然後檢查鍵盤是否顯示如下:

@Override
public void onGlobalLayout() {
    Configuration config = getResources().getConfiguration();

    // Dont allow the default keyboard to show up
    if (config.keyboardHidden != Configuration.KEYBOARDHIDDEN_YES) {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(mRootView.getWindowToken(), 0);
    }
}

最後一個解決方案可以顯示鍵盤一瞬間,並與選擇手柄混淆。

當鍵盤進入全屏時,不會調用onGlobalLayout。 為避免這種情況,請使用TextView#setImeOptions(int)或TextView XML聲明:

android:imeOptions="actionNone|actionUnspecified|flagNoFullscreen|flagNoExtractUi"

更新:剛剛找到哪些對話框用於永不顯示鍵盤並適用於所有版本:

getWindow().setFlags(WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM,
        WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM);






android android-asynctask android-networking