Wi-Fi 直连(也称为点对点或点对点)可让您的应用快速查找附近的设备并与其互动 蓝牙功能的范围之内。
通过 Wi-Fi Direct (P2P) API,应用无需 需要连接到网络或热点。 如果您的应用设计为安全、近距离网络的一部分,则 Wi-Fi 与传统的 Wi-Fi 临时相比,直连是更加合适的选项 可能出于以下原因:
- Wi-Fi 直连支持 WPA2 加密。(某些临时网络仅支持 WEP 加密)。
- 设备可以广播其提供的服务,这有助于其他 设备更容易发现合适的对等设备。
- 在确定哪台设备应成为该网络的群组所有者时, Wi-Fi 直连检查每台设备的电源管理、界面和服务 功能并利用这些信息选择可以处理 服务器职责。
- Android 不支持 Wi-Fi 临时模式。
本课介绍了如何使用 WLAN 点对点连接查找并连接到附近的设备。
设置应用权限
要使用 Wi-Fi 直连,请将
ACCESS_FINE_LOCATION
,
CHANGE_WIFI_STATE
,
ACCESS_WIFI_STATE
和
INTERNET
权限。
如果您的应用以 Android 13(API 级别 33)或更高版本为目标平台,也请添加
NEARBY_WIFI_DEVICES
权限。无线局域网
直接上传不需要互联网连接,但使用标准 Java
套接字,这需要 INTERNET
权限。因此,您需要以下权限才能使用 Wi-Fi 直连:
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.android.nsdchat" ... <!-- If your app targets Android 13 (API level 33) or higher, you must declare the NEARBY_WIFI_DEVICES permission. --> <uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" <!-- If your app derives location information from Wi-Fi APIs, don't include the "usesPermissionFlags" attribute. --> android:usesPermissionFlags="neverForLocation" /> <uses-permission android:required="true" android:name="android.permission.ACCESS_FINE_LOCATION" <!-- If any feature in your app relies on precise location information, don't include the "maxSdkVersion" attribute. --> android:maxSdkVersion="32" /> <uses-permission android:required="true" android:name="android.permission.ACCESS_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.CHANGE_WIFI_STATE"/> <uses-permission android:required="true" android:name="android.permission.INTERNET"/> ...
除上述权限外,以下 API 还要求启用位置信息模式:
设置广播接收器和对等连接管理器
要使用 Wi-Fi 直连,您需要监听告知
并在发生特定事件时触发应用在您的应用中,实例化
IntentFilter
,并将其设置为监听以下内容:
WIFI_P2P_STATE_CHANGED_ACTION
- 用于指示是否启用 Wi-Fi 直连
WIFI_P2P_PEERS_CHANGED_ACTION
- 表示可用的类似应用列表已更改。
WIFI_P2P_CONNECTION_CHANGED_ACTION
-
指示 Wi-Fi 直连连接的状态已更改。起始内容
对于 Android 10,这不是固定的。如果您的应用依赖于接收这些
在注册时广播,因为此类广播是固定的,请使用适当的
get
方法获取信息。 WIFI_P2P_THIS_DEVICE_CHANGED_ACTION
-
指示此设备的配置详细信息已更改。起始内容
对于 Android 10,这不是固定的。如果您的应用依赖于接收这些
在注册时广播,因为此类广播是固定的,请使用适当的
get
方法获取信息。
Kotlin
private val intentFilter = IntentFilter() ... override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.main) // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION) // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION) // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION) // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION) ... }
Java
private final IntentFilter intentFilter = new IntentFilter(); ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); // Indicates a change in the Wi-Fi Direct status. intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); // Indicates a change in the list of available peers. intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); // Indicates the state of Wi-Fi Direct connectivity has changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); // Indicates this device's details have changed. intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); ... }
在 onCreate()
方法结束时,获取 WifiP2pManager
的实例,并调用其 initialize()
方法。此方法会返回一个 WifiP2pManager.Channel
对象,稍后您将使用该对象
将应用连接到 Wi-Fi Direct 框架。
Kotlin
private lateinit var channel: WifiP2pManager.Channel private lateinit var manager: WifiP2pManager override fun onCreate(savedInstanceState: Bundle?) { ... manager = getSystemService(Context.WIFI_P2P_SERVICE) as WifiP2pManager channel = manager.initialize(this, mainLooper, null) }
Java
Channel channel; WifiP2pManager manager; @Override public void onCreate(Bundle savedInstanceState) { ... manager = (WifiP2pManager) getSystemService(Context.WIFI_P2P_SERVICE); channel = manager.initialize(this, getMainLooper(), null); }
现在,创建一个新的 BroadcastReceiver
类,您将使用该类监听更改
设为系统的 Wi-Fi 状态。在onReceive()
中
方法,添加一个条件来处理上面列出的每个状态变化。
Kotlin
override fun onReceive(context: Context, intent: Intent) { when(intent.action) { WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1) activity.isWifiP2pEnabled = state == WifiP2pManager.WIFI_P2P_STATE_ENABLED } WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // The peer list has changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. } WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> { (activity.supportFragmentManager.findFragmentById(R.id.frag_list) as DeviceListFragment) .apply { updateThisDevice( intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE) as WifiP2pDevice ) } } } }
Java
@Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION.equals(action)) { // Determine if Wi-Fi Direct mode is enabled or not, alert // the Activity. int state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1); if (state == WifiP2pManager.WIFI_P2P_STATE_ENABLED) { activity.setIsWifiP2pEnabled(true); } else { activity.setIsWifiP2pEnabled(false); } } else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // The peer list has changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { // Connection state changed! We should probably do something about // that. } else if (WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION.equals(action)) { DeviceListFragment fragment = (DeviceListFragment) activity.getFragmentManager() .findFragmentById(R.id.frag_list); fragment.updateThisDevice((WifiP2pDevice) intent.getParcelableExtra( WifiP2pManager.EXTRA_WIFI_P2P_DEVICE)); } }
最后,添加代码,以便在发生以下情况时注册 intent 过滤器和广播接收器:
您的主 activity 处于活跃状态,并在该 activity 暂停时取消注册这些 activity。
建议这样做的最佳地点是onResume()
onPause()
方法。
Kotlin
/** register the BroadcastReceiver with the intent values to be matched */ public override fun onResume() { super.onResume() receiver = WiFiDirectBroadcastReceiver(manager, channel, this) registerReceiver(receiver, intentFilter) } public override fun onPause() { super.onPause() unregisterReceiver(receiver) }
Java
/** register the BroadcastReceiver with the intent values to be matched */ @Override public void onResume() { super.onResume(); receiver = new WiFiDirectBroadcastReceiver(manager, channel, this); registerReceiver(receiver, intentFilter); } @Override public void onPause() { super.onPause(); unregisterReceiver(receiver); }
启动对等设备发现
如需使用 Wi-Fi 点对点连接开始搜索附近的设备,请调用 discoverPeers()
。此方法将
以下参数:
- 您获得的
WifiP2pManager.Channel
您在初始化点对点 mManager 时收到的响应 - 包含方法的
WifiP2pManager.ActionListener
实现 成功和失败的发现进行调用。
Kotlin
manager.discoverPeers(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } override fun onFailure(reasonCode: Int) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } })
Java
manager.discoverPeers(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Code for when the discovery initiation is successful goes here. // No services have actually been discovered yet, so this method // can often be left blank. Code for peer discovery goes in the // onReceive method, detailed below. } @Override public void onFailure(int reasonCode) { // Code for when the discovery initiation fails goes here. // Alert the user that something went wrong. } });
请记住,这仅启动对等设备发现。通过
discoverPeers()
方法会启动发现过程,然后
会立即返回结果。如果对等设备发现进程处于
通过在提供的操作监听器中调用方法成功启动。
此外,在启动连接或点对点组被启动之前,发现一直处于活跃状态
。
获取对等设备列表
现在编写获取并处理对等设备列表的代码。第一页
实现 WifiP2pManager.PeerListListener
接口,提供有关 Wi-Fi 直连的对等设备的信息
。此信息还让您的应用可以确定同伴加入或加入的时间
离开网络。以下代码段展示了这些操作
与类似应用相关:
Kotlin
private val peers = mutableListOf<WifiP2pDevice>() ... private val peerListListener = WifiP2pManager.PeerListListener { peerList -> val refreshedPeers = peerList.deviceList if (refreshedPeers != peers) { peers.clear() peers.addAll(refreshedPeers) // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. (listAdapter as WiFiPeerListAdapter).notifyDataSetChanged() // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.isEmpty()) { Log.d(TAG, "No devices found") return@PeerListListener } }
Java
private List<WifiP2pDevice> peers = new ArrayList<WifiP2pDevice>(); ... private PeerListListener peerListListener = new PeerListListener() { @Override public void onPeersAvailable(WifiP2pDeviceList peerList) { List<WifiP2pDevice> refreshedPeers = peerList.getDeviceList(); if (!refreshedPeers.equals(peers)) { peers.clear(); peers.addAll(refreshedPeers); // If an AdapterView is backed by this data, notify it // of the change. For instance, if you have a ListView of // available peers, trigger an update. ((WiFiPeerListAdapter) getListAdapter()).notifyDataSetChanged(); // Perform any other updates needed based on the new list of // peers connected to the Wi-Fi P2P network. } if (peers.size() == 0) { Log.d(WiFiDirectActivity.TAG, "No devices found"); return; } } }
现在,修改广播接收器的 onReceive()
方法,以便在收到具有 WIFI_P2P_PEERS_CHANGED_ACTION
操作的 intent 时调用 requestPeers()
。您
因此需要以某种方式将此监听器传入接收器。一种方法是发送
作为广播接收器构造函数的参数。
Kotlin
fun onReceive(context: Context, intent: Intent) { when (intent.action) { ... WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() mManager?.requestPeers(channel, peerListListener) Log.d(TAG, "P2P peers changed") } ... } }
Java
public void onReceive(Context context, Intent intent) { ... else if (WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION.equals(action)) { // Request available peers from the wifi p2p manager. This is an // asynchronous call and the calling activity is notified with a // callback on PeerListListener.onPeersAvailable() if (mManager != null) { mManager.requestPeers(channel, peerListListener); } Log.d(WiFiDirectActivity.TAG, "P2P peers changed"); }... }
现在,一个包含 WIFI_P2P_PEERS_CHANGED_ACTION
intent 操作的 intent
触发对更新后的对等列表的请求。
连接到对等设备
如需连接到对等节点,请创建一个新的 WifiP2pConfig
对象,并将数据从
WifiP2pDevice
,表示您要
连接。然后,调用 connect()
方法。
Kotlin
override fun connect() { // Picking the first device found on the network. val device = peers[0] val config = WifiP2pConfig().apply { deviceAddress = device.deviceAddress wps.setup = WpsInfo.PBC } manager.connect(channel, config, object : WifiP2pManager.ActionListener { override fun onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "Connect failed. Retry.", Toast.LENGTH_SHORT ).show() } }) }
Java
@Override public void connect() { // Picking the first device found on the network. WifiP2pDevice device = peers.get(0); WifiP2pConfig config = new WifiP2pConfig(); config.deviceAddress = device.deviceAddress; config.wps.setup = WpsInfo.PBC; manager.connect(channel, config, new ActionListener() { @Override public void onSuccess() { // WiFiDirectBroadcastReceiver notifies us. Ignore for now. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "Connect failed. Retry.", Toast.LENGTH_SHORT).show(); } }); }
如果群组中的每台设备都支持 Wi-Fi 直连,您就不需要
在连接时明确要求输入群组密码。允许设备
不支持 Wi-Fi 直连功能加入群组,但您需要
通过调用
requestGroupInfo()
,以
如以下代码段所示:
Kotlin
manager.requestGroupInfo(channel) { group -> val groupPassword = group.passphrase }
Java
manager.requestGroupInfo(channel, new GroupInfoListener() { @Override public void onGroupInfoAvailable(WifiP2pGroup group) { String groupPassword = group.getPassphrase(); } });
请注意,在以下代码中实现的 WifiP2pManager.ActionListener
:
connect()
方法仅会在调用
成功或失败如需监听连接状态的变化,请实现
WifiP2pManager.ConnectionInfoListener
接口。
其 onConnectionInfoAvailable()
回调会在
更改连接。在将多台设备连接到时
一台设备(例如有三个或更多玩家的游戏,或聊天应用)、一台设备
已被指定为“群组拥有者”。您可以将特定设备指定为
按照
创建群组部分。
Kotlin
private val connectionListener = WifiP2pManager.ConnectionInfoListener { info -> // String from WifiP2pInfo struct val groupOwnerAddress: String = info.groupOwnerAddress.hostAddress // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
Java
@Override public void onConnectionInfoAvailable(final WifiP2pInfo info) { // String from WifiP2pInfo struct String groupOwnerAddress = info.groupOwnerAddress.getHostAddress(); // After the group negotiation, we can determine the group owner // (server). if (info.groupFormed && info.isGroupOwner) { // Do whatever tasks are specific to the group owner. // One common case is creating a group owner thread and accepting // incoming connections. } else if (info.groupFormed) { // The other device acts as the peer (client). In this case, // you'll want to create a peer thread that connects // to the group owner. } }
现在,返回到广播接收器的 onReceive()
方法,并修改此部分
用于监听 WIFI_P2P_CONNECTION_CHANGED_ACTION
intent 的操作。
收到此 intent 时,调用 requestConnectionInfo()
。这是一个异步调用
这样,您作为
参数。
Kotlin
when (intent.action) { ... WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> { // Connection state changed! We should probably do something about // that. mManager?.let { manager -> val networkInfo: NetworkInfo? = intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO) as NetworkInfo if (networkInfo?.isConnected == true) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener) } } } ... }
Java
... } else if (WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION.equals(action)) { if (manager == null) { return; } NetworkInfo networkInfo = (NetworkInfo) intent .getParcelableExtra(WifiP2pManager.EXTRA_NETWORK_INFO); if (networkInfo.isConnected()) { // We are connected with the other device, request connection // info to find group owner IP manager.requestConnectionInfo(channel, connectionListener); } ...
创建群组
如果您希望运行应用的设备充当某个应用的群组所有者
包含旧版设备(即不支持与
Wi-Fi 直连 - 具体步骤与
连接到同行部分,但需要创建新的
WifiP2pManager.ActionListener
使用 createGroup()
而非 connect()
。调用
WifiP2pManager.ActionListener
相同,如以下代码段所示:
Kotlin
manager.createGroup(channel, object : WifiP2pManager.ActionListener { override fun onSuccess() { // Device is ready to accept incoming connections from peers. } override fun onFailure(reason: Int) { Toast.makeText( this@WiFiDirectActivity, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT ).show() } })
Java
manager.createGroup(channel, new WifiP2pManager.ActionListener() { @Override public void onSuccess() { // Device is ready to accept incoming connections from peers. } @Override public void onFailure(int reason) { Toast.makeText(WiFiDirectActivity.this, "P2P group creation failed. Retry.", Toast.LENGTH_SHORT).show(); } });
注意 :如果网络中的所有设备都支持 Wi-Fi
直接,您可以在每台设备上使用 connect()
方法,因为
方法,然后创建群组并自动选择群组所有者。
创建群组后,您可以
requestGroupInfo()
,用于检索对等项的详细信息
包括设备名称和连接状态。