Android Wifi P2P 入门

您所在的位置:网站首页 wlan直连需要打开热点吗 Android Wifi P2P 入门

Android Wifi P2P 入门

2023-10-17 23:10| 来源: 网络整理| 查看: 265

 

学习资料:

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、设置相关权限:

因为在建立 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

 

 



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3