Wi-Fi Direct (Wi-Fi P2P) cho phép các thiết bị kết nối trực tiếp với nhau mà không cần Access Point (router). Android cung cấp API WifiP2pManager để khám phá, kết nối, truyền dữ liệu peer-to-peer.
Quyền cần thiết (Manifest)
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<!-- Android 13+ -->
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES"
android:usesPermissionFlags="neverForLocation" />
<uses-feature android:name="android.hardware.wifi.direct" android:required="true" />
1. BroadcastReceiver lắng nghe sự kiện P2P
public class WiFiDirectBroadcastReceiver extends BroadcastReceiver {
private final WifiP2pManager manager;
private final WifiP2pManager.Channel channel;
private final MainActivity activity;
public WiFiDirectBroadcastReceiver(WifiP2pManager manager, WifiP2pManager.Channel channel, MainActivity activity) {
this.manager = manager;
this.channel = channel;
this.activity = activity;
}
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
activity.updateWiFiDirectState(state == WifiP2pManager.WIFI_P2P_STATE_ENABLED);
} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
// Danh sách peer thay đổi -> requestPeers()
if (manager != null) manager.requestPeers(channel, activity.peerListListener);
} else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
// Trạng thái kết nối thay đổi
NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
if (networkInfo.isConnected()) {
manager.requestConnectionInfo(channel, activity.connectionInfoListener);
} else {
activity.onConnectionLost();
}
} else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
// Thông tin thiết bị mình thay đổi
WifiP2pDevice device = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
activity.updateThisDevice(device);
}
}
}
2. MainActivity - Khởi tạo Manager, Channel, UI
public class MainActivity extends AppCompatActivity implements
WifiP2pManager.PeerListListener,
WifiP2pManager.ConnectionInfoListener {
private WifiP2pManager manager;
private WifiP2pManager.Channel channel;
private BroadcastReceiver receiver;
private IntentFilter intentFilter;
private Button btnToggle, btnDiscover, btnConnect;
private ListView lvPeers;
private TextView tvStatus, tvDeviceInfo;
private ArrayAdapter peersAdapter;
private List peers = new ArrayList<>();
private WifiP2pDevice selectedDevice;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Views
btnToggle = findViewById(R.id.btnToggle);
btnDiscover = findViewById(R.id.btnDiscover);
btnConnect = findViewById(R.id.btnConnect);
lvPeers = findViewById(R.id.lvPeers);
tvStatus = findViewById(R.id.tvStatus);
tvDeviceInfo = findViewById(R.id.tvDeviceInfo);
peersAdapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1);
lvPeers.setAdapter(peersAdapter);
// Khởi tạo WifiP2pManager
manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE);
channel = manager.initialize(this, getMainLooper(), null);
// BroadcastReceiver
receiver = new WiFiDirectBroadcastReceiver(manager, channel, this);
intentFilter = new IntentFilter();
intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION);
// UI Listeners
btnToggle.setOnClickListener(v -> toggleWiFiDirect());
btnDiscover.setOnClickListener(v -> discoverPeers());
btnConnect.setOnClickListener(v -> connectToSelected());
lvPeers.setOnItemClickListener((parent, view, position, id) -> {
selectedDevice = peers.get(position);
btnConnect.setEnabled(true);
});
}
private void toggleWiFiDirect() {
WifiManager wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
if (wifiManager.isWifiEnabled()) {
wifiManager.setWifiEnabled(false);
btnToggle.setText("Bật Wi-Fi Direct");
} else {
wifiManager.setWifiEnabled(true);
btnToggle.setText("Tắt Wi-Fi Direct");
}
}
private void discoverPeers() {
if (manager != null && channel != null) {
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() {
@Override public void onSuccess() { tvStatus.setText("Đang khám phá..."); }
@Override public void onFailure(int reason) { tvStatus.setText("Khám phá thất bại: " + reason); }
});
}
}
private void connectToSelected() {
if (selectedDevice == null) return;
WifiP2pConfig config = new WifiP2pConfig();
config.deviceAddress = selectedDevice.deviceAddress;
config.wps.setup = WpsInfo.PBC; // Push Button Config
manager.connect(channel, config, new WifiP2pManager.ActionListener() {
@Override public void onSuccess() { tvStatus.setText("Đang kết nối..."); }
@Override public void onFailure(int reason) { tvStatus.setText("Kết nối thất bại: " + reason); }
});
}
// PeerListListener callback
@Override
public void onPeersAvailable(WifiP2pDeviceList peerList) {
peers.clear();
peers.addAll(peerList.getDeviceList());
peersAdapter.clear();
for (WifiP2pDevice d : peers) {
peersAdapter.add(d.deviceName + (d.isGroupOwner() ? " (GO)" : ""));
}
peersAdapter.notifyDataSetChanged();
}
// ConnectionInfoListener callback
@Override
public void onConnectionInfoAvailable(WifiP2pInfo info) {
if (info.groupFormed) {
String role = info.isGroupOwner ? "Group Owner (Server)" : "Client";
tvStatus.setText("Đã kết nối - " + role + "\nGroup Owner IP: " + info.groupOwnerAddress.getHostAddress());
// TODO: Mở Socket Server/Client tại info.groupOwnerAddress
}
}
public void updateWiFiDirectState(boolean enabled) {
tvStatus.setText(enabled ? "Wi-Fi Direct: BẬT" : "Wi-Fi Direct: TẮT");
btnDiscover.setEnabled(enabled);
btnConnect.setEnabled(enabled);
}
public void updateThisDevice(WifiP2pDevice device) {
tvDeviceInfo.setText("Thiết bị này: " + device.deviceName + "\nMAC: " + device.deviceAddress);
}
public void onConnectionLost() {
tvStatus.setText("Mất kết nối");
}
@Override protected void onResume() {
super.onResume();
registerReceiver(receiver, intentFilter);
}
@Override protected void onPause() {
super.onPause();
unregisterReceiver(receiver);
}
}
3. Layout (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView android:id="@+id/tvDeviceInfo" android:layout_width="match_parent" android:layout_height="wrap_content"
android:textSize="14sp" android:layout_marginBottom="8dp" />
<TextView android:id="@+id/tvStatus" android:layout_width="match_parent" android:layout_height="wrap_content"
android:textSize="16sp" android:textStyle="bold" android:layout_marginBottom="16dp" />
<LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content"
android:orientation="horizontal" android:layout_marginBottom="16dp">
<Button android:id="@+id/btnToggle" android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_weight="1" android:text="Bật Wi-Fi Direct" />
<Button android:id="@+id/btnDiscover" android:layout_width="0dp" android:layout_height="wrap_content"
android:layout_weight="1" android:layout_marginStart="8dp" android:text="Khám phá" android:enabled="false" />
</LinearLayout>
<Button android:id="@+id/btnConnect" android:layout_width="match_parent" android:layout_height="wrap_content"
android:text="Kết nối thiết bị đã chọn" android:enabled="false" android:layout_marginBottom="16dp" />
<ListView android:id="@+id/lvPeers" android:layout_width="match_parent" android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
4. Truyền dữ liệu sau khi kết nối (Socket)
Khi onConnectionInfoAvailable trả về groupFormed=true:
- Group Owner (Server): Mở
ServerSocketlắng nghe port (ví dụ 8888). - Client: Kết nối
Socketđếninfo.groupOwnerAddressport 8888.
// Server Thread (Group Owner)
new Thread(() -> {
try (ServerSocket server = new ServerSocket(8888)) {
Socket client = server.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
String msg = in.readLine();
runOnUiThread(() -> tvStatus.append("\nNhận: " + msg));
} catch (IOException e) { e.printStackTrace(); }
}).start();
// Client Thread
new Thread(() -> {
try (Socket socket = new Socket(info.groupOwnerAddress.getHostAddress(), 8888)) {
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("Hello từ Client!");
} catch (IOException e) { e.printStackTrace(); }
}).start();
Lưu ý quan trọng
| Vấn đề | Giải pháp |
|---|---|
| Khám phá không tìm thấy peer | Cả 2 thiết bị phải bật Wi-Fi Direct, cấp quyền Location, cùng kênh WiFi |
| Kết nối thất bại | Thử WPS PBC (config.wps.setup = WpsInfo.PBC) hoặc Keypad |
| Android 10+ hạn chế | Chỉ hoạt động khi app foreground, cần Location permission |
| IP Group Owner | Luôn dùng info.groupOwnerAddress, không hardcode IP |
Kết luận
WiFi Direct cho phép truyền dữ liệu tốc độ cao, khoảng cách gần mà không cần internet/router. Cốt lõi: WifiP2pManager + BroadcastReceiver + Socket. Phù hợp cho chia sẻ file offline, game multiplayer local, IoT device pairing.