Bài viết này minh họa cách truyền dữ liệu giữa hai thiết bị Android qua WiFi sử dụng mô hình Socket Server-Client. Bạn cần tạo hai dự án riêng biệt: một đóng vai Server (lắng nghe kết nối) và một đóng vai Client (kết nối đến Server).
Phần 1: Xây dựng ứng dụng Server
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project, điền thông tin cần thiết và tạo dự án (ví dụ: package com.server.myapplication.server).
Bước 2: Thiết kế giao diện (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="Nhập tin nhắn"
android:inputType="text" />
<Button
android:id="@+id/btnSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="GỬI" />
</RelativeLayout>
Bước 3: Viết code logic (src/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();
}
serverThread = new Thread(new ServerThread());
serverThread.start();
btnSend.setOnClickListener(v -> {
String message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new SendThread(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 ServerThread implements Runnable {
@Override
public void run() {
Socket socket;
try {
serverSocket = new ServerSocket(SERVER_PORT);
runOnUiThread(() -> {
tvMessages.setText("Chưa kết nối");
tvIP.setText("IP: " + SERVER_IP);
tvPort.setText("Port: " + SERVER_PORT);
});
socket = serverSocket.accept();
output = new PrintWriter(socket.getOutputStream());
input = new BufferedReader(new InputStreamReader(socket.getInputStream()));
runOnUiThread(() -> tvMessages.setText("Đã kết nối\n"));
new Thread(new ReceiveThread()).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveThread 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 ngắt kết nối, khởi động lại server
serverThread = new Thread(new ServerThread());
serverThread.start();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class SendThread implements Runnable {
private final String message;
SendThread(String message) { this.message = message; }
@Override
public void run() {
output.write(message + "\n");
output.flush();
runOnUiThread(() -> {
tvMessages.append("Server: " + message + "\n");
etMessage.setText("");
});
}
}
}
Bước 4: Cấu hình quyền (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>
Phần 2: Xây dựng ứng dụng Client
Bước 1: Tạo dự án mới
Tương tự, tạo dự án mới với package com.client.myapplication.client.
Bước 2: Thiết kế giao diện (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="Địa chỉ IP Server"
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="Cổng (Port)"
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="Kết nối đến 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="Nhập tin nhắn"
android:inputType="text" />
<Button
android:id="@+id/btnSend"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:text="GỬI" />
</RelativeLayout>
Bước 3: Viết code logic (src/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 ConnectThread());
clientThread.start();
});
btnSend.setOnClickListener(v -> {
String message = etMessage.getText().toString().trim();
if (!message.isEmpty()) {
new Thread(new SendThread(message)).start();
}
});
}
class ConnectThread 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("Đã kết nối\n"));
new Thread(new ReceiveThread()).start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
class ReceiveThread implements Runnable {
@Override
public void run() {
while (true) {
try {
final String message = input.readLine();
if (message != null) {
runOnUiThread(() -> tvMessages.append("Server: " + message + "\n"));
} else {
// Server ngắt kết nối, thử kết nối lại
clientThread = new Thread(new ConnectThread());
clientThread.start();
return;
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
class SendThread implements Runnable {
private final String message;
SendThread(String message) { this.message = message; }
@Override
public void run() {
output.write(message + "\n");
output.flush();
runOnUiThread(() -> {
tvMessages.append("Client: " + message + "\n");
etMessage.setText("");
});
}
}
}
Bước 4: Cấu hình quyền (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>
Cách chạy thử
- Cài đặt ứng dụng Server lên thiết bị Android thứ nhất.
- Cài đặt ứng dụng Client lên thiết bị Android thứ hai.
- Đảm bảo cả hai thiết bị kết nối cùng một mạng WiFi.
- Chạy Server trước, ghi lại địa chỉ IP và Port hiển thị trên màn hình.
- Chạy Client, nhập IP và Port của Server, nhấn "Kết nối đến Server".
- Sau khi kết nối thành công, bạn có thể gửi tin nhắn hai chiều giữa hai thiết bị.
Lưu ý quan trọng: Code trên sử dụng API cũ (Android Support Library). Khi triển khai thực tế, nên cập nhật lên AndroidX, sử dụng ExecutorService hoặc Coroutines thay vì Thread thô, và xử lý vòng đời Activity cẩn thận để tránh rò rỉ bộ nhớ.