Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Sử dụng WiFi Direct (Wi-Fi P2P) trên Android

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ở ServerSocket lắng nghe port (ví dụ 8888).
  • Client: Kết nối Socket đến info.groupOwnerAddress port 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.