Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีส่งข้อมูลผ่าน Wi-Fi ใน Android โดยการเขียนโปรแกรม

ภาพรวม

บทช่วยสอนนี้แสดงวิธีส่งข้อมูลระหว่างอุปกรณ์ Android สองเครื่องผ่าน Wi-Fi โดยใช้ Socket Programming จำเป็นต้องสร้าง 2 โปรเจกต์แยกกัน คือ Server (ฝั่งรับ) และ Client (ฝั่งส่ง) ทั้งสองต้องอยู่ในเครือข่าย Wi-Fi เดียวกัน

ส่วน Server (ฝั่งรับข้อมูล)

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

เปิด Android Studio → File → New Project → เลือก Empty Activity → กรอกข้อมูลโปรเจกต์ (Package name: com.server.myapplication.server)

ขั้นตอนที่ 2: จัดการ Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tvIP"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvPort"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvIP"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvConnectionStatus"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/tvPort"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/etMessage"
        android:layout_below="@+id/tvConnectionStatus"
        android:inputType="textMultiLine"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/btnSend"
        android:hint="Enter Message"
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

ขั้นตอนที่ 3: เขียนโค้ด MainActivity.java

package com.server.myapplication.server;

import android.annotation.SuppressLint;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;

@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
    private ServerSocket serverSocket;
    private Thread serverThread = null;
    private TextView tvIP, tvPort, tvMessages;
    private EditText etMessage;
    private Button btnSend;
    public static String SERVER_IP = "";
    public static final int SERVER_PORT = 8080;
    private PrintWriter output;
    private BufferedReader input;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        tvIP = findViewById(R.id.tvIP);
        tvPort = findViewById(R.id.tvPort);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);

        try {
            SERVER_IP = getLocalIpAddress();
        } catch (UnknownHostException e) {
            e.printStackTrace();
        }

        tvIP.setText("IP: " + SERVER_IP);
        tvPort.setText("Port: " + SERVER_PORT);

        serverThread = new Thread(new ServerRunnable());
        serverThread.start();

        btnSend.setOnClickListener(v -> {
            String message = etMessage.getText().toString().trim();
            if (!message.isEmpty()) {
                new Thread(new SendRunnable(message)).start();
            }
        });
    }

    private String getLocalIpAddress() throws UnknownHostException {
        WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
        WifiInfo wifiInfo = wifiManager.getConnectionInfo();
        int ipInt = wifiInfo.getIpAddress();
        return InetAddress.getByAddress(
            ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN).putInt(ipInt).array()
        ).getHostAddress();
    }

    class ServerRunnable implements Runnable {
        @Override
        public void run() {
            Socket socket;
            try {
                serverSocket = new ServerSocket(SERVER_PORT);
                runOnUiThread(() -> tvMessages.setText("รอการเชื่อมต่อ...\n"));

                socket = serverSocket.accept();
                output = new PrintWriter(socket.getOutputStream());
                input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                runOnUiThread(() -> tvMessages.append("เชื่อมต่อแล้ว\n"));
                new Thread(new ReceiveRunnable()).start();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    class ReceiveRunnable implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    final String message = input.readLine();
                    if (message != null) {
                        runOnUiThread(() -> tvMessages.append("Client: " + message + "\n"));
                    } else {
                        // Client ปิดการเชื่อมต่อ เริ่มรอใหม่
                        runOnUiThread(() -> tvMessages.append("Client ตัดการเชื่อมต่อ\n"));
                        serverThread = new Thread(new ServerRunnable());
                        serverThread.start();
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class SendRunnable implements Runnable {
        private final String message;
        SendRunnable(String message) { this.message = message; }
        @Override
        public void run() {
            output.write(message + "\n");
            output.flush();
            runOnUiThread(() -> {
                tvMessages.append("Server: " + message + "\n");
                etMessage.setText("");
            });
        }
    }
}

ขั้นตอนที่ 4: กำหนด Permission ใน AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.server.myapplication.server">

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <uses-permission android:name="android.permission.INTERNET"/>

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity" android:label="Server">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

ส่วน Client (ฝั่งส่งข้อมูล)

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

สร้างโปรเจกต์อีกอัน (Package name: com.client.myapplication.client)

ขั้นตอนที่ 2: จัดการ Layout (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:layout_margin="16dp"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etIP"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="IP-Address"
        android:inputType="text" />

    <EditText
        android:id="@+id/etPort"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/etIP"
        android:hint="Port No"
        android:inputType="number" />

    <Button
        android:id="@+id/btnConnect"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_below="@+id/etPort"
        android:layout_marginTop="16dp"
        android:text="Connect To Server" />

    <TextView
        android:id="@+id/tvMessages"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/etMessage"
        android:layout_below="@+id/btnConnect"
        android:inputType="textMultiLine"
        android:textAppearance="@style/Base.TextAppearance.AppCompat.Medium" />

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_above="@+id/btnSend"
        android:hint="Enter Message"
        android:inputType="text" />

    <Button
        android:id="@+id/btnSend"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:text="SEND" />
</RelativeLayout>

ขั้นตอนที่ 3: เขียนโค้ด MainActivity.java

package com.client.myapplication.client;

import android.annotation.SuppressLint;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

@SuppressLint("SetTextI18n")
public class MainActivity extends AppCompatActivity {
    private Thread clientThread = null;
    private EditText etIP, etPort, etMessage;
    private TextView tvMessages;
    private Button btnSend;
    private String SERVER_IP;
    private int SERVER_PORT;
    private PrintWriter output;
    private BufferedReader input;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        etIP = findViewById(R.id.etIP);
        etPort = findViewById(R.id.etPort);
        tvMessages = findViewById(R.id.tvMessages);
        etMessage = findViewById(R.id.etMessage);
        btnSend = findViewById(R.id.btnSend);
        Button btnConnect = findViewById(R.id.btnConnect);

        btnConnect.setOnClickListener(v -> {
            tvMessages.setText("");
            SERVER_IP = etIP.getText().toString().trim();
            SERVER_PORT = Integer.parseInt(etPort.getText().toString().trim());
            clientThread = new Thread(new ConnectRunnable());
            clientThread.start();
        });

        btnSend.setOnClickListener(v -> {
            String message = etMessage.getText().toString().trim();
            if (!message.isEmpty()) {
                new Thread(new SendRunnable(message)).start();
            }
        });
    }

    class ConnectRunnable implements Runnable {
        @Override
        public void run() {
            try {
                Socket socket = new Socket(SERVER_IP, SERVER_PORT);
                output = new PrintWriter(socket.getOutputStream());
                input = new BufferedReader(new InputStreamReader(socket.getInputStream()));

                runOnUiThread(() -> tvMessages.setText("เชื่อมต่อแล้ว\n"));
                new Thread(new ReceiveRunnable()).start();
            } catch (IOException e) {
                e.printStackTrace();
                runOnUiThread(() -> tvMessages.setText("เชื่อมต่อล้มเหลว: " + e.getMessage()));
            }
        }
    }

    class ReceiveRunnable implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    final String message = input.readLine();
                    if (message != null) {
                        runOnUiThread(() -> tvMessages.append("Server: " + message + "\n"));
                    } else {
                        runOnUiThread(() -> tvMessages.append("Server ตัดการเชื่อมต่อ\n"));
                        return;
                    }
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    class SendRunnable implements Runnable {
        private final String message;
        SendRunnable(String message) { this.message = message; }
        @Override
        public void run() {
            output.write(message + "\n");
            output.flush();
            runOnUiThread(() -> {
                tvMessages.append("Client: " + message + "\n");
                etMessage.setText("");
            });
        }
    }
}

ขั้นตอนที่ 4: กำหนด Permission ใน AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="https://schemas.android.com/apk/res/android"
    package="com.client.myapplication.client">

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity" android:label="Client">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>

วิธีทดสอบ

  1. ติดตั้งแอป Server บนอุปกรณ์เครื่องแรก และแอป Client บนอุปกรณ์เครื่องที่สอง
  2. รันแอป Server ก่อน จะแสดง IP Address และ Port (8080)
  3. รันแอป Client ใส่ IP และ Port ของ Server แล้วกด "Connect To Server"
  4. เมื่อเชื่อมต่อสำเร็จ สามารถพิมพ์ข้อความและกด SEND ได้ทั้งสองฝั่ง
หมายเหตุ: โค้ดนี้ใช้ Thread แบบเดิม สำหรับแอปจริงควรใช้ Coroutines หรือ ExecutorService ร่วมกับ Lifecycle-aware components เพื่อจัดการ Thread ให้ปลอดภัยกว่า