Android Wifi P2P 入门

学习资料:

Demo 下载地址:https://github.com/mengzhinan/WiFi_P2P_test

GoogleAndroid Doc:https://developer.android.google.cn/guide/topics/connectivity/wifip2p

背景介绍:

Wifi P2P (peer to peer):义为 Wifi 点对点,也叫 Wifi 直连(Wifi Direct),他是 Wifi Display(投屏) 应用的技术基础。

官方描述:

使用 WLAN 直连 (P2P) 技术,可以让具备相应硬件的 Android 4.0(API 级别 14)或更高版本设备在没有中间接入点的情况下,通过 WLAN 进行直接互联。使用这些 API,您可以实现支持 WLAN P2P 的设备间相互发现和连接,从而获得比蓝牙连接更远距离的高速连接通信效果。对于多人游戏或照片共享等需要在用户之间共享数据的应用而言,这一技术非常有用。

总结以下优点:

1、有比蓝牙更远的传输距离。未测试

2、有比蓝牙更快速的数据传输速度,更大的带宽。未测试

3、只需要打开 Wifi 即可,不需要加入任何网络或 AP,即可实现对等点连接通讯。

可实现通过 Wifi 连接,同时使用数据网络的场景,比喻:手机遥控无人机的同时,无人机需要访问远程服务器上传数据。

Wifi P2P 架构:

虽然上面提到两台或多台 Android 设备通过 Wifi P2P 通讯时不需要加入任何网络,但是 Wifi P2P 协议还是需要组件网络才能发现对方并建立 TCP 连接通讯的。在组网和通讯阶段一共有 3 个角色:

1、P2P Group Owner,或称为群主,充当服务端,并需要创建 ServerSocket 等待客户端的连接,获得 IO 流与客户端通讯或转发消息给其他客户端。

2、P2P Client,或称为组员,充当客户端,需要创建 Socket 与服务器通讯。

3、P2P Device,在上面的过程中,服务器端和客户端都是一个独立的设备,拥有唯一的设备特征信息。

4、广播接收器:

WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION:检查 Wi-Fi P2P 是否已启用。Android 4.0 以上系统才有此功能。

WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION:对等设备发生变化,一般是在调用 discoverPeers 方法后发送此广播。在此广播中,你可以调用 requestPeers 方法,获得扫描到的对等设备列表。

WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION:连接状态发生变化,一般在调用 connect 或 cancelConnect 方法时会发送此广播。状态共有 5 种:WifiP2pDevice.AVAILABLE、WifiP2pDevice.INVITED、WifiP2pDevice.CONNECTED、WifiP2pDevice.FAILED 和 WifiP2pDevice.UNAVAILABLE 。

当判断连接信息为连接状态时,即 networkInfo.isConnected() ,你应当继续请求连接的具体信息 mManager.requestConnectionInfo(...),然后获得群主的详细设备信息,建立 Socket 通讯。

WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION:此设备的WiFi状态更改回调,应用可使用 requestDeviceInfo() 来检索当前连接信息。

在未组网之前,是不存在群主、组员之称的。只有在设备尝试发现并连接对方时,系统才会通过 P2P 协议尝试使多端设备组件为一个群组,并自动确定某一个设备为群主。但是本人在实测过程中发现,是需要先有群主,才会加入组员组网通讯的。

更底层的原理参考:https://blog.csdn.net/wirelessdisplay/article/details/53365377

连接流程:

绘制了一张流程图,描述我 Demo 的连接过程。

服务端流程:

server

客户端流程:

Android 代码层面介绍:

1、设置相关权限:

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

因为在建立 P2P 连接后,需要建立 Socket 通讯,所以需要 INTERNET 权限。

2、注册广播,有 4 个核心广播 Action:

@Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) {
            // Check to see if Wi-Fi is enabled and notify appropriate activity
            // 检查 Wi-Fi P2P 是否已启用
            int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1);
            boolean isEnabled = (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED);
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onWifiP2pEnabled(isEnabled);
            }
        } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // Call WifiP2pManager.requestPeers() to get a list of current peers
            WifiP2pDeviceList wifiP2pDeviceList = intent.getParcelableExtra(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
            if (mWifiP2PListener != null && wifiP2pDeviceList != null) {
                mWifiP2PListener.onPeersAvailable(wifiP2pDeviceList.getDeviceList());
            }
            // 异步方法
            WifiP2PHelper.getInstance(context).requestPeers();
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            // Respond to new connection or disconnections
            // 链接状态变化回调
            // 此广播 会和 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 同时回调
            // 注册广播、连接成功、连接失败 三种时机都会调用
            // 应用可使用 requestConnectionInfo()、requestNetworkInfo() 或 requestGroupInfo() 来检索当前连接信息。
            NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
            if (networkInfo != null
                    && networkInfo.isConnected()
                    && mManager != null
                    && mWifiP2PListener != null) {
                WifiP2PHelper.getInstance(context).requestConnectInfo();
            } else {
                if (mWifiP2PListener != null) {
                    mWifiP2PListener.onConnectionInfoAvailable(null);
                }
            }
        } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) {
            // Respond to this device's wifi state changing
            // 此设备的WiFi状态更改回调
            // 此广播 会和 WIFI_P2P_CONNECTION_CHANGED_ACTION 同时回调
            // 注册广播、连接成功、连接失败 三种时机都会调用
            // 应用可使用 requestDeviceInfo() 来检索当前连接信息。
            WifiP2pDevice wifiP2pDevice = intent.getParcelableExtra(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE);
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onSelfDeviceAvailable(wifiP2pDevice);
            }
        }
    }

3、初始化 Wifi P2P:

private WifiP2PHelper(Context context) {
    if (context == null || context.getApplicationContext() == null) {
        throw new IllegalArgumentException("context is null exception.");
    }
    mApplicationContext = context.getApplicationContext();

    mManager = (WifiP2pManager) mApplicationContext.getSystemService(Context.WIFI_P2P_SERVICE);
    // 将此应用注册到 WLAN P2P 框架
    mChannel = mManager.initialize(mApplicationContext, Looper.getMainLooper(), null);
    mReceiver = new WifiP2PBroadCastReceiver(mManager, mChannel);
}

初始化阶段的核心就是调用 manager.initialize(...) 方法,得到群组内通讯的通道对象 channel。

4、服务端创建群组:

public void createGroup() {
    mManager.createGroup(mChannel, new WifiP2pManager.ActionListener() {
        @Override
        public void onSuccess() {
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onCreateGroup(true);
            }
        }

        @Override
        public void onFailure(int reason) {
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onCreateGroup(false);
            }
        }
    });
}

5、客户端扫描对等设备:

public void discover() {
    mManager.discoverPeers(mChannel, new WifiP2pManager.ActionListener() {
        @Override
        public void onSuccess() {
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onDiscoverPeers(true);
            }
        }

        @Override
        public void onFailure(int reason) {
            if (mWifiP2PListener != null) {
                mWifiP2PListener.onDiscoverPeers(false);
            }
        }
    });
}

6、客户端收到扫描成功广播后,请求扫描到的结果。尝试了解设备,获取到连接广播,在连接成功时,请求连接的详细信息,获取服务端的 IP 地址,建立 Socket 连接。

} else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) {
            // Call WifiP2pManager.requestPeers() to get a list of current peers
            WifiP2pDeviceList wifiP2pDeviceList = intent.getParcelableExtra(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
            if (mWifiP2PListener != null && wifiP2pDeviceList != null) {
                mWifiP2PListener.onPeersAvailable(wifiP2pDeviceList.getDeviceList());
            }
            // 异步方法
            WifiP2PHelper.getInstance(context).requestPeers();
        } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) {
            // Respond to new connection or disconnections
            // 链接状态变化回调
            // 此广播 会和 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION 同时回调
            // 注册广播、连接成功、连接失败 三种时机都会调用
            // 应用可使用 requestConnectionInfo()、requestNetworkInfo() 或 requestGroupInfo() 来检索当前连接信息。
            NetworkInfo networkInfo = intent.getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO);
            if (networkInfo != null
                    && networkInfo.isConnected()
                    && mManager != null
                    && mWifiP2PListener != null) {
                WifiP2PHelper.getInstance(context).requestConnectInfo();
            } else {
                if (mWifiP2PListener != null) {
                    mWifiP2PListener.onConnectionInfoAvailable(null);
                }
            }
        } 

7、服务器创建 ServerSocket 接收客户端,并死循环读取 InputStream 数据:

private void initSocket() {
        try {
            serverSocket = new ServerSocket(SERVER_PORT);
            // 需要设置为无限超时
//            serverSocket.setSoTimeout(10000);
            while (!isQuitReadClient) {
                socket = serverSocket.accept();
                inputStream = socket.getInputStream();
                outputStream = socket.getOutputStream();
                String text = "连接到客户端 -> " + PHONE_INFO;
                IOHelper.writeText(outputStream, text);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private void receive() {
        try {
            while (!isQuitReadMessage) {
                Thread.sleep(500);
                String text = IOHelper.readText(inputStream);
                if (TextUtils.isEmpty(text)) {
                    continue;
                }
                postToUI(text);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

8、客户端床架 Socket 连接服务端,并死循环读取 InputStream:

private void initSocket() {
        try {
            socket = new Socket(serverIP, SERVER_PORT);
            inputStream = socket.getInputStream();
            outputStream = socket.getOutputStream();
            String text = "连接到服务端 -> " + PHONE_INFO;
            IOHelper.writeText(outputStream, text);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void receive() {
        try {
            while (!isQuitReadMessage) {
                Thread.sleep(500);
                String text = IOHelper.readText(inputStream);
                if (TextUtils.isEmpty(text)) {
                    continue;
                }
                postToUI(text);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

Demo 效果图:

服务端:

客户端:

服务端接收连接确认对话框:

坑点:

1、需要先创建群组,客户端才可以连接并加入组,否则连接不上。

2、服务端 ServerSocket 在等待时不要设置超时,否则遇到客户端连不上问题时难以排查。

3、如果服务端退出时没有移除群组,或客户端退出时没有断开连接,在下次连接时会出现连不上问题,不确定原因。

4、客户端首次连接服务端时,服务端会弹出请求对话框。服务端同意后才会建立连接。

Demo 下载地址:https://github.com/mengzhinan/WiFi_P2P_test

猜你喜欢

转载自blog.csdn.net/fesdgasdgasdg/article/details/106559510